deckhearth/.convoys/unify-glass-panel-surfaces/brief-4-bulk-selection-toolbar.md
varutasu 66a4d7b721
docs(convoys): unify-glass-panel-surfaces + cleanup palette — conductor seed + architect plan (#119)
* docs(convoys): seed unify-glass-panel-surfaces + cleanup-card-item-list-and-share-modal-palette

Two convoy seeds opened as follow-ups to the 2026-06-04 design pass
(#116 corner-border-light → #117 site-wide sweep → #118 card vibrancy
reduction). Both were called out in #117's PR body as deferred and are
now formally tracked.

## unify-glass-panel-surfaces

Migrates remaining panel-shaped surfaces to the gradient-border
corner-light treatment so the app shares one surface vocabulary.

The audit's key insight: `<GlassSurface>` (`components/ui/GlassSurface.js`)
predates the corner-light pattern. Because `<Modal>`, `<StatCard>`,
and the landing-page feature/collection cards all delegate to it,
upgrading the primitive cascades to ~10 visible surfaces at once.

7 briefs, multitask-parallel after Brief 1 lands:

1. `<GlassSurface>` primitive upgrade — BLOCKING for 3, 4
2. Auth form cards (login.js, signup.js)
3. Floating popovers (mobile drawer, sidebar profile dropdown,
   UserMenu dropdown)
4. BulkSelectionToolbar (currently `bg-white border-gray-200` —
   invisible in dark mode)
5. `.card`-class consumers (4 pages); decision to ratify whether
   to retire `.card` entirely or keep as documented opaque fallback
6. Landing nav bar — wrong pattern; should use existing
   `.page-header-glass` class
7. `forbidden-bespoke-glass-surface` CI grep gate — prevents
   regression after the migration ships

## cleanup-card-item-list-and-share-modal-palette

Targeted palette cleanup for two files whose interiors weren't
addressed in #117:

1. `CardItem.js` list-mode (L183–284) — entirely hardcoded
   Tailwind palette (`bg-purple-50`, `border-gray-200`, `text-gray-{500-900}`,
   `bg-blue-100 text-blue-800` etc.); unreadable / off-brand in dark mode.
2. `ShareModal.js` interior rows — purple avatar circles, gray-50
   permission row (invisible in dark mode), blue-600 Copy-link button,
   gray text labels.

Token-only swap. 2 parallel briefs, no architect / IA / UX needed
(no design decisions — palette to design tokens).

## Sequencing note

The two convoys are independent and can run in parallel. The audit
agent's recommended sequencing (Brief 1 of `unify-glass-panel-surfaces`
first) is encoded in the multitask `slice_dependencies` blocks.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(convoys): architect plan + 7 briefs for unify-glass-panel-surfaces

Appends the ## Architecture section to .convoys/unify-glass-panel-surfaces.md
ratifying the 4 open Decisions, and writes 7 implementer-brief files under
.convoys/unify-glass-panel-surfaces/.

Decisions ratified:
  D1. <GlassSurface> grows a cornerLights='subtle'|'chrome'|'none' prop,
      default 'subtle' (4-layer gradient-border per PR #118 recipe).
  D2. Retire .card entirely; migrate all 8 consumers to .glass-panel.
  D3. Mobile drawer uses subtle tier (.glass-panel-strong).
  D4. CI gate scopes to JSX inline-style usage only; 3-entry allowlist
      for documented chrome exceptions.

Briefs (1-7, all <100 LOC each, disjoint files):
  1. Upgrade <GlassSurface> primitive (no deps, blocks 3+4)
  2. Migrate auth form cards (parallel-safe)
  3. Migrate floating popovers (sidebar dropdown, mobile drawer,
     TopSearchBar UserMenu)
  4. Migrate BulkSelectionToolbar + interior token sweep
  5. Retire .card class; migrate 8 consumers
  6. Migrate landing nav bar to .page-header-glass
  7. forbidden-bespoke-glass-surface CI gate (runs LAST)

Boot-the-brief check: all 4-layer recipes verified against post-PR-#118
styles/globals.css; all box-shadow preservation chains verified against
current Layout.js + TopSearchBar.js; .card deletion verified clean
(single rule at L729-733); CI gate shape modeled on existing
forbidden-modal-shell-without-primitive job.

Architecture is read-only output — no application code touched.
Awaiting human gate 1 (plan approval) before implementers run.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs(convoys): fix Brief 3 TopSearchBar UserMenu elevation token

Self-review recheck against current TopSearchBar.js (L211-222) found
that the UserMenu dropdown uses var(--elevation-pronounced), not
var(--elevation-ambient) as Brief 3's initial sample showed. Brief 3
now documents the verbatim shape (pronounced + role="menu" +
aria-label + overflow-hidden) and the convoy's risk-list note is
updated to reflect all three popovers' verbatim chains.

No semantic change to the plan; just an accuracy fix the
implementer of Brief 3 needs to ship the right code on the first try.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 14:09:40 -05:00

7.7 KiB

convoy brief_number depends_on files
unify-glass-panel-surfaces 4
1
components/BulkSelectionToolbar.js

Brief 4: Migrate BulkSelectionToolbar to .glass-panel-strong + token sweep

Goal (1 sentence)

Replace the bulk-select toolbar's bg-white border-gray-200 Tailwind shape with .glass-panel-strong rounded-2xl so the floating toolbar is legible in dark mode, and sweep its interior hardcoded gray/red Tailwind classes (text-gray-700, hover:bg-gray-100, text-red-600 hover:bg-red-50) to token-driven inline styles + nav-item-hover for consistency with the rest of the post-PR-#117 surfaces.

Files in scope (do not edit anything else)

  • components/BulkSelectionToolbar.js — the entire file. Specifically:
    • L47 outer toolbar <div> — swap bg-white rounded-xl shadow-2xl border border-gray-200 to glass-panel-strong rounded-2xl shadow-2xl. (Keep shadow-2xl for now — the toolbar floats over arbitrary content and the extra weight is intentional. .glass-panel-strong's var(--rim-light-inner), var(--elevation-ambient) chain doesn't include it; chain it via inline override.)
    • L60 divider — swap bg-gray-300 to a CSS-var-driven divider color (backgroundColor: 'var(--border)' inline, since bg-gray-300 doesn't theme).
    • L53 selection-count label — swap text-gray-700 to style={{ color: 'var(--text-primary)' }}.
    • L108 more-actions trigger — swap text-gray-600 hover:text-gray-800 hover:bg-gray-100 to use var(--text-secondary) + nav-item-hover, with rounded-lg upgraded to rounded-xl.
    • L117 more-actions dropdown — swap bg-white rounded-lg shadow-xl border border-gray-200 to glass-panel-strong rounded-xl. (Drop shadow-xl; the class provides shadow. Keep border semantics out — the class's gradient border is the new look.)
    • L118-141 menu items — swap text-gray-700 hover:bg-gray-100 to var(--text-primary) + nav-item-hover className.
    • L144 menu divider — swap border-gray-200 to borderColor: 'var(--border)'.
    • L146-157 destructive menu item — swap text-red-600 hover:bg-red-50 to color: 'var(--accent-danger)' (verify the token exists in globals.css; if not, fall back to rgb(239, 68, 68) and document) + nav-item-hover className.
    • L160-end "Clear Selection" button — sweep any remaining text-gray-* / bg-gray-* hardcodes the same way.

Out of scope: the parent components that consume BulkSelectionToolbar (e.g. pages/my-cards.js, components/CollectionPageView.js); the props contract (selectedCount, selectedCards, all the onBulk* callbacks); any of the SVG icon paths.

Conventions to follow

  • Outer toolbar:

    // Before:
    <div className="bg-white rounded-xl shadow-2xl border border-gray-200 px-3 sm:px-6 py-3 sm:py-4 flex items-center justify-between sm:justify-start sm:space-x-4 sm:min-w-96">
    
    // After:
    <div
      className="glass-panel-strong rounded-2xl px-3 sm:px-6 py-3 sm:py-4 flex items-center justify-between sm:justify-start sm:space-x-4 sm:min-w-96"
      style={{
        // Chain shadow-2xl-equivalent depth onto the class's existing
        // rim+ambient stack so the floating toolbar still reads as
        // elevated over arbitrary page content.
        boxShadow:
          'var(--rim-light-inner), var(--elevation-pronounced)',
      }}
    >
    

    Rationale: shadow-2xl is a hardcoded RGB; var(--elevation-pronounced) is the system equivalent and respects the theme.

  • More-actions dropdown:

    // Before:
    <div className="absolute bottom-full right-0 mb-2 bg-white rounded-lg shadow-xl border border-gray-200 py-2 min-w-48">
    
    // After:
    <div className="glass-panel-strong absolute bottom-full right-0 mb-2 rounded-xl py-2 min-w-48">
    

    No inline-style override needed — the class's default var(--rim-light-inner), var(--elevation-ambient) is the right weight for an inner dropdown.

  • Menu items (the <button> rows inside the dropdown):

    // Before (non-destructive item):
    <button className="w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100 flex items-center space-x-2">
    
    // After:
    <button
      className="nav-item-hover w-full px-4 py-2 text-left text-sm flex items-center space-x-2"
      style={{ color: 'var(--text-primary)' }}
    >
    
    // Before (destructive item):
    <button className="w-full px-4 py-2 text-left text-sm text-red-600 hover:bg-red-50 flex items-center space-x-2">
    
    // After:
    <button
      className="nav-item-hover w-full px-4 py-2 text-left text-sm flex items-center space-x-2"
      style={{ color: 'var(--accent-danger)' }}
    >
    
  • Verify --accent-danger exists before using it. Quick check: search styles/globals.css for --accent-danger. If absent, the PR #117 sweep likely defines it; if it's still absent post-#117, use color: 'rgb(239, 68, 68)' (the literal Tailwind red-500 RGB) and add a note in the PR description requesting follow-up to introduce the token in a separate PR. Do NOT add the token in this brief — token additions belong in a design-system PR.

  • Verify nav-item-hover exists — it's defined in styles/globals.css and used widely post-PR #117. If for some reason this brief dispatches before PR #117 lands, escalate to the conductor — that's a dependency violation (Brief 4 depends_on: [1] but transitively depends on the post-PR-#117 token tier).

  • Verbatim children: SVG icon paths, button text labels, click handler bindings, the VOCAB.MY_COLLECTION / VOCAB.REMOVE_FROM_MY_COLLECTION imports, the setShowActions(false) flow — all stay byte-identical.

  • px-2 sm:px-3 py-2 action buttons (the 3 colored quick-action buttons: Collection / Deck / My Collection): leave them alone. They use --accent-flame, --accent-gold, --accent-wood already and the onMouseEnter/onMouseLeave swap is a known pattern. Touching them is scope expansion.

Acceptance criteria

  • Outer toolbar <div> uses glass-panel-strong rounded-2xl and an inline boxShadow matching the verbatim shape above.
  • More-actions dropdown <div> uses glass-panel-strong rounded-xl and no inline background / border / shadow style.
  • All text-gray-{600,700,800} classes within the toolbar are replaced with style={{ color: 'var(--text-{primary,secondary}) ' }} or nav-item-hover className.
  • All hover:bg-gray-{50,100} classes are replaced with nav-item-hover className.
  • The destructive "Delete Selected" item uses var(--accent-danger) (or the documented rgb(239,68,68) fallback) — NOT text-red-600.
  • The L60 divider uses style={{ backgroundColor: 'var(--border)' }} — NOT bg-gray-300.
  • The 3 quick-action buttons (Collection / Deck / My Collection at L64-101) are UNCHANGED. The "Clear Selection" button at the end may need a small text-color swap; that's in scope. Everything else is left alone.
  • Dark-mode verification: open the bulk-select toolbar (select 2+ cards on /my-cards or /collection/[id]), confirm it renders legibly with corner catch-lights and proper text contrast in BOTH light and dark themes. Attach before/after dark-mode screenshots to the PR description.
  • npm run lint + npm run test:run (23+/23+, post Brief 1) pass.
  • No edits to files outside components/BulkSelectionToolbar.js.

Rationale (≤3 sentences)

The toolbar is currently invisible in dark mode because bg-white doesn't theme — this PR is a correctness fix as much as a design unification. Sweeping the interior hardcoded grays + reds in the same PR is cheap (the file is small, ~200 lines) and prevents a follow-up convoy from having to revisit the file. The 3 colored quick-action buttons stay as-is because they use the right tokens already and a token sweep there is scope expansion.