Unify text truncation behind a single Truncate primitive - #3660
Merged
Conversation
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
commented
Aug 25, 2026
tom2drum
left a comment
Collaborator
Author
There was a problem hiding this comment.
| 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
- 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
commented
Aug 25, 2026
tom2drum
left a comment
Collaborator
Author
There was a problem hiding this comment.
| 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
tom2drum
commented
Aug 25, 2026
tom2drum
left a comment
Collaborator
Author
There was a problem hiding this comment.
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Replaces the four scattered text-truncation components —
HashStringShorten,HashStringShortenDynamic,TruncatedText,TruncatedTextTooltip— with one public toolkit primitive,Truncate, that dispatches on atypeprop (middledefault /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 acrossshared/andtoolkit/, and had divergent interfaces — this unifies them behind one honest API.TruncatedTextTooltipbecomes the toolkit-internalOverflowTooltipprimitive (still used directly bytag/badge, which wrap arbitrary children);shorten-stringmoves into the truncation dir. The entity hub keeps itstruncationprop and maps toTruncatein one place, so the ~115 entity call-sites are untouched, and its tooltip config is memoized to preserve the leafReact.memoin table rows. The tooltip contract is now uniform across all types: shown only when the value is truncated;tooltip.contentoverrides 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-toolkitpackage: theTruncatedTextandTruncatedTextTooltipexports are removed and replaced byTruncate. No impact on Blockscout deployment config. Hard cutover — no deprecation shims.Additional information
tooltip.contentoverride no longer forces the tooltip — the trigger is uniformly "when truncated". PreviouslyHashStringShortenDynamic/TruncatedTextshowed a tooltip whenever content was supplied even if not truncated.TokenEntity,PoolEntity,TokenInventoryItem, both beacon-chain components); relying on CI to confirm visual-test baselines. Local visual checks (incl. AddressEntity) passed.