Skip to content

feat(collection): a binder of your own, and the set picker's Binders tab (#1074) - #1126

Merged
Doberjohn merged 8 commits into
masterfrom
feature/1074-binders-inside
Nov 3, 2026
Merged

Doberjohn merged 8 commits into
masterfrom
feature/1074-binders-inside

Conversation

@Doberjohn

@Doberjohn Doberjohn commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

Part of #1074 (PR 2 of 3: Steps 9 to 22 of the issue). Everything here sits behind VITE_SHOW_BINDERS, which stays off in Production until #1075's Step 23.

What it does

  • A link opens one of your binders. ?binder=<scope>&custom=<id> narrows the pool to the binder's rules, heads the panel with its own numbers ("Complete binder"), and names it in the toolbar beside its cover's medal. A set picked inside it keeps the binder, read as "Amber · Set 12".
  • The set picker's Binders tab. My collection first, then your shelf in its order, each row with its cover, name and Complete percentage, the open one ringed in gold. Opening one clears the page's filters and search in the same write. Three tabs where promo sets exist, two where they don't.
  • A binder that isn't on your shelf says so in the binder's place, with Open My collection. A failed read says "Couldn't load your binders." with Try again. A friend's binder shows none of yours: the param goes and the picker has no Binders tab.
  • The toolbar's button grows for a binder's name (owner, 2026-11-03), taking the room from the search field and keeping its right edge on the panel's. Its 111px cut every name to "Amb…" once the medal took 18px. The words stop at 170px, so the longest names give the search field its room back.
  • Privacy. No binder's name reaches an attribute or PostHog: each row and trigger is named by one screen-reader-only line and carries ph-no-capture.

Off means not loaded

Every module under features/collection/binders/ is reached only through a dynamic import the bundler folds on SHOW_BINDERS, and binderCodeGuard (build-guards.ts) fails a flag-off build in which a page can load any of it. Checked with a flag-off production build: the binder chunks are emitted but nothing loads them.

Size budgets (owner's pick on the card, 2026-11-03)

  • New group Binders JS (lazy), 8 kB: 5.27 kB.
  • User-facing JS 460 → 462 kB: 461.44 kB. About 1.2 kB of the rise is collectionStats, which was its own chunk on master and now joins Browse's.
  • Collection and reveals JS stays 67 kB: 66.82 kB.

Design check

Screenshots at 1920, 390 and 393, on the real page over the test stubs with the deployed card art: Binders Design Check (a private page on the owner's Claude account).

Tests

  • Unit: BinderTab, SetPicker (tabs, opening tab, arrows round three tabs, toolbar label and medal, no name in attributes), CollectionSetPanel (Complete binder), CollectionBinderSection (empty states name the binder), useBrowseCollection (params, friend guard, unknown and failed binder, import over the whole pool, pickerNamesBinder), BrowseToolbar (the group grows only for a binder). Each new assertion was checked to fail with its source line broken.
  • E2E: new binders.spec.ts, 8 tests on every project, over a stubbed collection_binders (e2e/helpers/binders.ts). Green on chromium and mobile-chrome, as is collection-mode.spec.ts.
  • Stories for BinderMedal, BinderTab, UnknownBinder, the picker's binder states and the panel's. check:stories now also scans a feature's other folders, such as collection/binders.
  • Local gates: typecheck, lint, check:design, check:stories, knip, the web, inkweave-ui and script suites, CodeScene (strict, passed).

After merge

The owner sets Preview VITE_SHOW_BINDERS=true, so staging and PR previews show binders. Production stays off.


Generated by Claude Code


Summary by cubic

Adds binders of your own: ?custom=<id> opens a binder, narrowing the pool to its rules and naming it in the toolbar beside its cover's medal, and the set picker gains a Binders tab. Everything sits behind VITE_SHOW_BINDERS, which stays off in Production.

New Features

  • The set picker's Binders tab shows My collection, then your shelf in order, each row with its cover, name, and completion; opening one clears the page's filters and search.
  • A binder not on your shelf says so with Open My collection, a failed read offers Try again (a chunk that resolves empty counts as one), and a friend's binder shows none of yours.
  • No binder's name reaches an attribute or PostHog: rows and triggers are screen-reader-only and carry ph-no-capture; Vercel's tools drop custom too.
  • The toolbar's picker button grows for a binder's name, taking room from the search field.
  • A failed shelf read retries on the next visit, and a binder's rules drop the dual-ink switch exactly where the page does.

Rollout

  • Binder modules load only with the flag on; the bundler folds them away otherwise, and binderCodeGuard fails a flag-off build that could load any.
  • After merge, set VITE_SHOW_BINDERS=true in Preview; Production stays off until the follow-up.

Written for commit a1649a7. Summary will update on new commits.

View guided diff

…tab (#1074)

`?custom=<id>` opens one of your binders: its rules narrow the pool, its own
numbers head the panel ("Complete binder"), and the toolbar names it beside its
cover's medal. The set picker gains a Binders tab, My collection first and then
your shelf in order, each row with its cover, its name and how complete it is;
opening one clears the page's filters, and a set picked inside it keeps the
binder, read as "Amber · Set 12". A binder that isn't on your shelf says so with
a way back, and a failed read says so with a Try again. A friend's binder shows
none of yours.

No binder's name reaches an attribute or PostHog: every row and trigger is named
by one screen-reader-only line and carries `ph-no-capture`.

It all stays behind `VITE_SHOW_BINDERS`. With the flag off the bundler folds
every binder module away, which `binderCodeGuard` holds the build to, so the
lazy chunks ship under their own size budget (owner, 2026-11-03).

The toolbar's button grows for a binder's name, taking the room from the search
field and keeping its right edge at the panel's, since 71px cut every name short
(owner, 2026-11-03).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DjrxDXnQkU2y3Yc3iHWuWs
@vercel

vercel Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
inkweave Ready Ready Preview Nov 3, 2026 9:01pm UTC

Request Review

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

🎨 Design-surface change detected

This PR touches files mirrored by the Inkweave design system project.
After merge, refresh the mirror so it doesn't drift out of sync.

What changed:

  • 📖 CLAUDE.md — design language sections may have shifted

To refresh: open the design system Claude project and prompt:

Refresh against the latest of Doberjohn/inkweave@master. Focus on the files this PR touched: CLAUDE.md

This is a reminder, not a blocker. Configured in .github/workflows/design-system-drift.yml.

@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
User-facing JS 451.96 KB (+0.58% 🔺)
Supabase SDK (lazy) 26.98 KB (0%)
PostHog SDK (lazy) 100.91 KB (+0.01% 🔺)
Entry chunk 28 KB (+0.05% 🔺)
Collection and reveals JS 65.29 KB (+1.11% 🔺)
Release notes page 9.33 KB (0%)
Card data JSON 110.37 KB (0%)
Collection index JSON 63.83 KB (0%)
Collection set chunks 158.18 KB (0%)
Binders JS (lazy) 5.27 KB (+100% 🔺)

codescene-delta-analysis[bot]

This comment was marked as outdated.

CI's knip checks exports since #1119, and five binder names were exported
only for their own module: the e2e helper's REST path, the shelf's forget,
and three story fixtures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DjrxDXnQkU2y3Yc3iHWuWs

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 59 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/web/src/features/collection/SetPicker.tsx">

<violation number="1" location="apps/web/src/features/collection/SetPicker.tsx:399">
P3: If the list chunk is still loading on first open, `useFocusOpenChoiceOnMount` sees only the active tab; this row never receives focus when it resolves. Focus the selected binder when the lazy list mounts.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

View guided diff | Re-trigger cubic

Comment thread apps/web/e2e/tests/binders.spec.ts Resolved
Comment thread apps/web/e2e/tests/binders.spec.ts Resolved
- A binder chunk that resolves empty, as a stale one does in production, is a
  failed load with its Try again, never a module the page calls into.
- A one-set binder whose rules hold no card counts that set, empty, so its
  numbers stop shimmering.
- Vercel's tools drop `custom` from the address, as PostHog masks it.
- A failed shelf read is read again the next time collection mode wants it.
- A binder's rules drop the dual-ink switch where the page drops it, so its
  analytics kinds and its line name only what the binder applies.
- A binder row the guard leaves out reaches Sentry.
- A count changed inside a binder keeps its cards, so no rule reruns.
- The picker's slot fills its group, so a short binder name keeps Collection
  and the gear at the panel's right edge.
- Comments drop the issue tags that only recorded where code came from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DjrxDXnQkU2y3Yc3iHWuWs
codescene-delta-analysis[bot]

This comment was marked as outdated.

Doberjohn commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner Author

Review pass on 944178e: of the 18 threads, 13 are fixed, each behaviour change with a test that fails without it (the binders.spec Prettier thread in part: its double blank line). These stay as they are, with the reasons on their threads:

  • Binders tab tallies per render (Claude): the list renders once per opening of the menu, and a press outside closes it, so no count changes while it is up.
  • Playstyle page's own filter copy (cubic): it predates this PR; folding it into applyFilters.ts belongs in a separate cleanup.
  • Prettier line widths (cubic, three threads): format:check runs in no CI job or hook here, and much of the codebase already runs past 100 columns.
  • Focus when the Binders list loads late (cubic): the list's chunk is warmed as the picker mounts, so this needs a menu opened during the chunk's first download, and focus then sits on the Binders tab, one arrow from the rows.

Generated by Claude Code

@Doberjohn Doberjohn added the full-e2e label Nov 3, 2026 — with Claude
…s-inside

E2E_TESTS.md's total: master's 401 tests in 36 files plus this branch's
8 tests in binders.spec.ts, 409 in 37 (playwright --list agrees).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DjrxDXnQkU2y3Yc3iHWuWs
…s-inside

E2E_TESTS.md's total: master's 411 tests in 37 files plus this branch's
8 tests in binders.spec.ts, 419 in 38 (playwright --list agrees).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DjrxDXnQkU2y3Yc3iHWuWs
@Doberjohn Doberjohn added full-e2e and removed full-e2e labels Nov 3, 2026 — with Claude
codescene-delta-analysis[bot]

This comment was marked as outdated.

…cts (#1074)

On WebKit's production build the binder showed a moment before the
toolbar named it, so the helper's check of which button was there picked
"Binder set", which then went, and the click waited out the test
(Full E2E, 3 Nov, "My collection closes the binder", flaky). Each caller
now names its button, and the click waits for that one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DjrxDXnQkU2y3Yc3iHWuWs
codescene-delta-analysis[bot]

This comment was marked as outdated.

…s-inside

#1120 landed first, so the User-facing JS budget goes to 463 kB, the
owner's pick for whichever of #1120 and #1126 merged second
(2026-11-03). Master measures 460.18 kB; with the binders it measures
462,802 B in a build with the Bundle Size job's env.

E2E_TESTS.md's total: master's 419 tests in 37 files plus this branch's
8 tests in binders.spec.ts, 427 in 38 (playwright --list agrees).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DjrxDXnQkU2y3Yc3iHWuWs

Copy link
Copy Markdown
Owner Author

Full E2E's WebKit job is red on one test this PR doesn't touch: synergy-detail-modal.spec.ts:329 "with reduced motion › a comparison opens and closes in place, and opens again" fails 3 of 3 attempts. After Back, the sampler catches card B on only 2 or 3 frames before it unmounts, so the four it expects never arrive. The test came to master with #1117, whose last two commits never had a cross-browser run; it's tracked in #1128, and its fix comes in a PR of its own. Firefox and mobile-safari pass.

The run before (bc09808) also showed binders.spec.ts "My collection closes the binder" as flaky on WebKit. That one was this PR's: the helper checked which toolbar button was there before the toolbar had named the binder, and picked the one about to go. ab7a6b6 has each test name its button; on its re-run (41714075304) every binders test passed on WebKit, and only the #1128 test failed.


Generated by Claude Code

@codescene-delta-analysis codescene-delta-analysis Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Gates Passed
6 Quality Gates Passed

See analysis details in CodeScene

Absence of Expected Change Pattern

  • inkweave/apps/web/src/features/collection/binderCards.ts is usually changed with: inkweave/apps/web/src/features/collection/collectionStats.test.ts
  • inkweave/apps/web/src/features/collection/CollectionSetPanel.stories.tsx is usually changed with: inkweave/apps/web/src/features/collection/collectionStats.test.ts
  • inkweave/apps/web/src/shared/lib/analytics.ts is usually changed with: inkweave/apps/web/src/shared/lib/tests/analytics.test.ts
  • inkweave/apps/web/src/pages/browseView.ts is usually changed with: inkweave/apps/web/src/pages/tests/browseView.test.ts
  • inkweave/apps/web/src/features/collection/collectionStats.ts is usually changed with: inkweave/apps/web/src/features/collection/collectionStats.test.ts

Quality Gate Profile: Pay Down Tech Debt
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

@Doberjohn
Doberjohn merged commit 603ce5d into master Nov 3, 2026
19 checks passed
@Doberjohn
Doberjohn deleted the feature/1074-binders-inside branch November 3, 2026 21:15
Doberjohn added a commit that referenced this pull request Nov 3, 2026
Brings in #1126 (binders). The docked toolbar keeps this branch's shape,
where Collection and its actions stay in the same elements in both
modes, and takes #1126's binder-sized picker group and slot
(pickerSpanStyle, pickerSlotStyle). E2E_TESTS.md's total is the sum of
both sides: 432 tests across 39 spec files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J57kHm4KbVuqMeE1Gsqocv

This branch was successfully deployed

1 active deployment
Preview — a1649a71 Deployed Nov 3, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant