Skip to content

CS-11207: Adopt Adorn overlay treatment for cards / atoms#4930

Draft
lukemelia wants to merge 8 commits into
mainfrom
cs-11207-adorn-ui-styles-interaction
Draft

CS-11207: Adopt Adorn overlay treatment for cards / atoms#4930
lukemelia wants to merge 8 commits into
mainfrom
cs-11207-adorn-ui-styles-interaction

Conversation

@lukemelia
Copy link
Copy Markdown
Contributor

Summary

  • Per-card operator-mode overlay rebuilt to the Adorn pattern from CS-11207: hover-only type-label tab carrying the per-card menu (3-dots), bottom-right rounded-square select chip, and box-shadow strokes that follow the card's actual border-radius (2px on hover, 4px on selected, darker accent on hover-on-selected).
  • Edit moves off the floating pencil into the per-card menu (next to View / Copy URL / Delete).
  • Stack-header multi-select chip restyled to teal with [✓ N ▼]; its dropdown gets a teal N Selected header above the existing Select All / Deselect All / Delete N items.
  • New base-overlay hooks (shouldDelayHoverClear, shouldSwallowCardClick) so the chrome stays mounted while a portal'd dropdown is open and so the dismiss-click doesn't ALSO open the card underneath.

Test plan

  • Cards-grid: hover a card and confirm the teal type-label tab + bottom-right circle, 2px stroke.
  • Click the select chip, confirm 4px stroke + filled check.
  • Hover a selected card, confirm stroke shifts to the darker accent.
  • Open the per-card 3-dots menu, walk the mouse from the chip → menu → menu item without the menu disappearing.
  • Click anywhere outside the open menu; the menu closes, the card underneath does not open.
  • Select 2+ cards; confirm the teal count chip in the stack header, open it, see the N Selected header + Select All / Deselect All / Delete N items.
  • Pre-existing overlay tests still pass (one integration + four acceptance tests updated to walk the new menu).

Deferred (out of tight scope)

  • The Figma-asset texture overlay Chris's POC layers on the bar — left out pending designer confirmation of whether that's intended for production and an asset commitable to the repo.
  • The JS-driven flip-left + portal-clone behavior the POC uses when the bar overflows past the card's right edge.

🤖 Generated with Claude Code

Replaces the per-card overlay chrome (floating select / edit / 3-dots
cluster) with the Adorn pattern from CS-11207: a hover-only type-label
tab at the top-left of the card carrying the per-card menu, and a
rounded-square selection chip at the bottom-right. Strokes are now
box-shadow outlines that follow the card's own border-radius (2px on
hover-unselected, 4px on selected, with the darker accent on
hover-on-selected). The stack-header multi-select pill becomes a teal
chip echoing the selection count, and its dropdown panel gets a "N
Selected" header above the existing actions.

Edit moves from a standalone pencil button into the per-card menu;
overlay tests are updated to walk the more-options menu to reach it.

The base Overlays component grows two hooks (shouldDelayHoverClear,
shouldSwallowCardClick) so the chrome stays mounted while a portal'd
dropdown is open and so the outside-click that dismisses the dropdown
doesn't ALSO open the underlying card. The velcro offset middleware
copies the underlying card's computed border-radius onto the overlay
so the selection stroke traces the same curve.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

Preview deployments

Host Test Results

    1 files  ±0      1 suites  ±0   1h 33m 30s ⏱️ -22s
2 724 tests ±0  2 706 ✅ ±0  15 💤 ±0  0 ❌ ±0  3 🔥 ±0 
2 743 runs  ±0  2 722 ✅ ±0  15 💤 ±0  3 ❌ ±0  3 🔥 ±0 

Results for commit 4cba7a2. ± Comparison against earlier commit affcbd5.

For more details on these errors, see this check.

Realm Server Test Results

    1 files  ±0      1 suites  ±0   10m 38s ⏱️ +16s
1 480 tests ±0  1 480 ✅ ±0  0 💤 ±0  0 ❌ ±0 
1 571 runs  ±0  1 571 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 4cba7a2. ± Comparison against earlier commit affcbd5.

lukemelia and others added 7 commits May 21, 2026 20:32
The selection count was previously rendered as a custom header element
above the Menu in CardHeader's dropdown content. Visually it didn't
align with the menu items below (the icon and label sat in a different
horizontal grid than Select All / Deselect All / Delete N items).

Promote MenuItem.header (which already existed on the type) into the
Menu component's renderer: items with header=true get a teal
background, pointer-events disabled on the row content so the row is
inert, and the check-icon column suppressed. Because the row is just
another MenuItem under the same `<ul>`, it inherits the same row
geometry — icon column lines up with the items below.

CardHeader no longer carries the headerText / custom header rendering;
stack-item.gts inserts a header MenuItem at the top of the utility
menu with the dark-circle-with-teal-check artwork as its icon.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the header MenuItem is the first child, it now picks up the
panel's border-top-{left,right}-radius via `inherit` (mirroring the
hover-state rule used by regular menu items at the top/bottom edges).
Without this, the teal background of the row was painting square
corners over the panel's rounded ones, leaving a pair of small white
"ears" at the top of the dropdown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the overlay renders over a FileDef target, the type-label tab
was always showing 'Card' (because the helper peeked the store as a
CardDef, missing the FileDef registered under file-meta). Switch the
peek to consult the correct subtree based on the existing
isFileMetaTarget detection, and let cardTypeDisplayName /
cardTypeIcon resolve against the FileDef's own constructor (FileDef
extends BaseDef, so the helpers already work — they were just being
fed undefined).

The per-card menu now swaps two labels when the target is a file:
'View card' → 'View file' and 'Copy Card URL' → 'Copy File URL'.
Edit and Delete are already suppressed for file targets by the
existing isButtonDisplayed gate.

Test: extend overlay-menu-items-test with a parent card that linksTo
MarkdownDef and a .md file in the realm; hover the rendered file
and assert (a) the type label tab shows the FileDef's displayName,
(b) the menu has 'View file' / 'Copy File URL' but not their card
counterparts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the icon gap for cards/files in the cards-grid (and any other
surface using prerendered-card-search). Previously the type-label tab
showed text without an icon for prerendered rows because cardTypeIcon
needs a runtime constructor reference we don't have until the instance
is loaded into the store.

prerendered-card-search now stamps `data-card-type-icon-html` with the
raw SVG markup the realm server already serves on PrerenderedCardData.
The overlay's getCardTypeIcon falls back to that attribute, wrapping
it in an htmlComponent (which caches by source string) so it renders
through the same `<TypeIcon class='adorn-label-icon' />` invocation
the loaded-instance path uses.

Applies equally to prerendered FileDef rows — they get a real
displayName-derived label and a real FileDef icon, not the generic
fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six things were broken by the prior commits — addressing all here:

1. lint:types — SelectionCheckmarkIcon was typed as
   TemplateOnlyComponent<{ Element: SVGElement }> but Icon expects
   SVGSVGElement. Widen.

2. Menu label mismatch — overlay implementation emitted 'Edit card'
   but several existing tests (and the new ones I added) assert on
   'Edit'. Rename the menu-item label to 'Edit' to match.

3. workspace-delete-multiple selector — the test's selectCard helper
   was waiting for `button.actions-item__button`, a CSS class the
   new chrome no longer uses. Switch to `[data-test-overlay-select]`
   which exists on the new selection chip and is stable.

4. card-delete test — the per-card menu lives inside the type-label
   tab, which only renders on hover. The test was selecting two cards
   then immediately clicking more-options on the first; with the new
   chrome the first card had been mouseleft and so its menu trigger
   was unmounted. Re-trigger mouseenter on the target card before
   clicking more-options.

5. Errored-card crash — `cardOrField.constructor.getDisplayName is
   not a function` when the store returns an error envelope instead
   of a BaseDef. Add a defensive check in peekInstance that requires
   constructor.getDisplayName before returning the instance.

6. spec-preview overlay clear — the hover-bridge 100ms delay added in
   the prior commit was applied as a static constant on the base
   Overlays class, which broke immediate-clear expectations in
   spec-preview / playground-panel / preview-panel. Convert the delay
   to a `hoverClearDelayMs` getter — base returns 0 (immediate, the
   original behavior), OperatorModeOverlays overrides to 100.

Also: FileDef instances always show "FILE" in the type-label tab,
because createFileDef on the client always instantiates the base
FileDef class — so cardTypeDisplayName(instance) returns 'File' even
for `linksTo(MarkdownDef)` targets. The realm server's indexer DOES
record the proper subclass display name in `display_names`, which
prerendered-card-search stamps onto the rendered wrapper. Reorder
getCardTypeName / getCardTypeIcon to prefer the DOM attribute over
the in-memory instance, so a FileDef row's chip says 'Markdown'
(or its actual subclass) instead of 'File'.

Fix the FileDef test's realm-content path (`notes.md` belongs under
`ParentWithFile/` since the relationship is './notes.md').

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CardsGrid file rows still read 'FILE' because the realm-server indexer
populates display_names only for FileDef *subclasses* — getDisplayNames
walks the prototype chain and stops at the base FileDef, so a row that
extracted as the bare FileDef class lands in the index with an empty
display_names array. cardType is then undefined and prerendered-card-
search doesn't stamp the DOM attribute, so my overlay's fallback hit
the generic 'File' label.

Also retract an earlier inaccurate claim — `createFileDef` is not on
the linksTo deserialization path; _createFromSerialized resolves to
the field's declared subclass. So linksTo(MarkdownDef) really does
yield a MarkdownDef instance with displayName 'Markdown'. The bare-
FileDef-only-shows-FILE case is specifically about *standalone*
files in CardsGrid (not linked from a card with linksTo(SubFileDef)).

For those rows the label now falls back to the URL extension ('MD',
'GTS', 'PNG', etc.) so each row reads differently. Still degrades to
'File' if no extension is parseable.

Picking order for the label:
1. The first source (DOM attr or in-store instance) that gives a
   *specific* name (anything other than the literal 'File' / 'Card').
2. The URL extension when the target is a file.
3. The raw DOM attr / instance name, even if generic.
4. 'File' / 'Card' final fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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