Skip to content

Unify text truncation behind a single Truncate primitive - #3660

Merged
tom2drum merged 7 commits into
mainfrom
worktree-truncation-refactoring
Aug 25, 2026
Merged

Unify text truncation behind a single Truncate primitive#3660
tom2drum merged 7 commits into
mainfrom
worktree-truncation-refactoring

Conversation

@tom2drum

Copy link
Copy Markdown
Collaborator

Description

Replaces the four scattered text-truncation components — HashStringShorten, HashStringShortenDynamic, TruncatedText, TruncatedTextTooltip — with one public toolkit primitive, Truncate, that dispatches on a type prop (middle default / middle-static / end) to three small internal technique modules. App code now chooses what to show (value) and how (type); the measurement/slice implementation stays internal. The four components had misleading names (they truncate any text, not just hashes), lived split across shared/ and toolkit/, and had divergent interfaces — this unifies them behind one honest API.

TruncatedTextTooltip becomes the toolkit-internal OverflowTooltip primitive (still used directly by tag/badge, which wrap arbitrary children); shorten-string moves into the truncation dir. The entity hub keeps its truncation prop and maps to Truncate in one place, so the ~115 entity call-sites are untouched, and its tooltip config is memoized to preserve the leaf React.memo in table rows. The tooltip contract is now uniform across all types: shown only when the value is truncated; tooltip.content overrides the shown content (not the trigger); tooltip={false} suppresses it.

Environment variables

None

Minimum API version

None

Breaking or incompatible changes

For external consumers of the @blockscout/ui-toolkit package: the TruncatedText and TruncatedTextTooltip exports are removed and replaced by Truncate. No impact on Blockscout deployment config. Hard cutover — no deprecation shims.

Additional information

  • Deliberate behavior change: a tooltip.content override no longer forces the tooltip — the trigger is uniformly "when truncated". Previously HashStringShortenDynamic/TruncatedText showed a tooltip whenever content was supplied even if not truncated.
  • DOM structure shifted at a few restructured sites (TokenEntity, PoolEntity, TokenInventoryItem, both beacon-chain components); relying on CI to confirm visual-test baselines. Local visual checks (incl. AddressEntity) passed.

Replace the four scattered truncation components (HashStringShorten,
HashStringShortenDynamic, TruncatedText, TruncatedTextTooltip) with one
public toolkit primitive, `Truncate`, that dispatches on a `type` prop to
three internal technique modules:

- `middle` (default) — measured middle-ellipsis (container width, binary search)
- `middle-static`    — fixed char count, pure string slice, SSR-safe, cheapest
- `end`              — measured end-ellipsis (CSS text-overflow)

App code now picks *what* (`value`) and *how* (`type`); implementation
details stay internal. `TruncatedTextTooltip` becomes the toolkit-internal
`OverflowTooltip` primitive (still used directly by tag/badge, which wrap
arbitrary children). `shorten-string` moves into the truncation dir.

The entity hub keeps its `truncation` prop and maps to `Truncate` in one
place, so the 115 entity call-sites are untouched. Its tooltip config is
memoized to preserve the leaf `React.memo` in table rows.

Tooltip contract is now uniform: shown only when the value is truncated;
`tooltip.content` overrides content (not the trigger); `tooltip={false}`
suppresses it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@tom2drum tom2drum left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ID Severity Axis Location
F1 major correctness src/shared/entities/components.tsx:240
F2 major correctness src/toolkit/components/truncation/TruncateMiddle.tsx:107
F3 major standards src/toolkit/components/truncation/Truncate.tsx:12
F4 major standards src/shell/metadata/generate.ts:13
F5 nit standards src/shared/entities/components.tsx:227
F6 nit correctness src/toolkit/components/truncation/TruncateMiddle.tsx:40
F7 nit standards src/toolkit/components/truncation/Truncate.tsx:8

No spec for this change — spec axis skipped.

blocker 0 · major 4 · nit 3 — Outcome: findings

— Reviewed by Cursor Grok 4.6

Comment thread src/shared/entities/components.tsx Outdated
Comment thread src/toolkit/components/truncation/TruncateMiddle.tsx Outdated
Comment thread src/toolkit/components/truncation/Truncate.tsx Outdated
Comment thread src/shell/metadata/generate.ts Outdated
Comment thread src/shared/entities/components.tsx
Comment thread src/toolkit/components/truncation/TruncateMiddle.tsx Outdated
Comment thread src/toolkit/components/truncation/Truncate.tsx Outdated
tom2drum and others added 2 commits August 25, 2026 11:17
- Force the tooltip when it carries actionable UI: add `always` to the
  tooltip config, honored by all three techniques, and set it wherever
  `tooltipContentAfter` is present so the advanced-filter link stays
  reachable on wide viewports (F1).
- Detect truncation by content, not length, in TruncateMiddle (F2).
- Name-export the three technique modules per the toolkit convention (F3).
- Move `shorten-string` to `toolkit/utils/texts`, name-export it, and
  re-export from the package index; update all importers (F4).
- Fix the stale fontWeight-observer comment and drop a reviewer-facing
  comment (F6, F7).

Also align OverflowTooltip's props with Chakra's Tooltip (content/positioning).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`Truncate type="end"` (TruncateEnd) was the only technique module missing
`asChild` on its Skeleton, so the non-loading fast path wrapped the text in
an extra `div`. Two visual regressions followed:

- TokenEntity: OverflowTooltip measured the wrapper div instead of the
  clipping span, so a truncated symbol reported not-truncated and its
  tooltip never opened. Add `asChild` so the text lives in the single
  clipping/measured span, matching TruncateMiddle/MiddleStatic.
- TokenInventory: the ID# row (previously a block <Link>) now wraps the
  inline-block Truncate, adding the baseline-descender gap and growing the
  row ~5px. Render that Truncate as `display="block"`, as before.

Verified in Docker against the CI baselines: both target tests plus a
53-test end/tail-truncation sweep pass, no screenshots updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@tom2drum tom2drum left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ID Severity Axis Location Ruling
F1 major correctness src/shared/entities/components.tsx verified
F2 major correctness src/toolkit/components/truncation/TruncateMiddle.tsx verified
F3 major standards src/toolkit/components/truncation/Truncate.tsx verified
F4 major standards src/shell/metadata/generate.ts verified
F5 nit standards src/shared/entities/components.tsx rejected-accepted
F6 nit correctness src/toolkit/components/truncation/TruncateMiddle.tsx verified
F7 nit standards src/toolkit/components/truncation/Truncate.tsx verified
F8 nit correctness src/toolkit/components/truncation/types.ts:8 open — regression from F1

Arbitration round. Spec axis skipped (no spec).

blocker 0 · major 0 · nit 1 open — Outcome: findings

— Reviewed by Cursor Grok 4.6

Comment thread src/toolkit/components/truncation/types.ts

@tom2drum tom2drum left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review clear

ID Severity Axis Location Ruling
F8 nit correctness src/toolkit/components/truncation/types.ts:8 verified

Arbitration round. Spec axis skipped (no spec).

blocker 0 · major 0 · nit 0 open — Outcome: clear

— Reviewed by Cursor Grok 4.6

@tom2drum
tom2drum merged commit 44bcebc into main Aug 25, 2026
@tom2drum
tom2drum deleted the worktree-truncation-refactoring branch August 25, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant