Skip to content

refactor(views): remove the inert list.tabs[] block from every view file - #1316

Merged
hotlong merged 2 commits into
mainfrom
claude/issue-1307-list-tabs-inert
Aug 26, 2026
Merged

hotlong merged 2 commits into
mainfrom
claude/issue-1307-list-tabs-inert

Conversation

@hotlong

@hotlong hotlong commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Description

list.tabs[] is inert in its entirety. PR #1304 removed the entries' label; this removes the rest of the entry — 60 entries, 48 icon: keys, 12 files — and corrects the five places in this repo that wrote the falsified model down.

The card's premise was re-measured before any edit, as its acceptance criteria demand, and it reproduced. Read directly out of node_modules/@objectstack/console@17.1.0 in this worktree (the lockfile pin has not moved):

  • The hardcoded map exists — dist/assets/src-*.js:
    _m={grid:$e,kanban:yt,calendar:ur,gallery:Ve,timeline:En,gantt:We,map:Yn,chart:qe}, passed to the switcher as viewTypeIcons:_m. Exactly the eight view types the card names.
  • The switcher never reads tabs — its strip comes from
    Dm({ definedViews: U.listViews || U.list_views || {}, primary: U.list, primaryId, savedViews, viewOverrides, fallbackTab }), and Dm's body builds descriptors from definedViews, primary and savedViews only. The primary is unshifted to the front and force-marked isDefault: true. Each tab's text is b(object, view.name, view.label) — the view's own label, i18n-resolved.
  • Control term: listViews matches 8 bundles, viewTypeIcons 2 — so the search reached the code. The single list.tabs hit in the whole dist is an i18n string key, "list.tabs": "Tabs", not a read.
  • Corroboration, unprompted by the card: the console logs, for an object list view, that a tabs-shaped userFilters block is ignored because "the view switcher owns the tab bar here" (ADR-0047 "views" mode). That is the same fact from the other side, and it is why ViewTabSchema.label is untouched here.

Type of Change

  • Code refactoring
  • Bug fix (non-breaking change which fixes an issue) — the false-belief comments and the two assertions resting on them
  • New feature
  • Breaking change
  • Documentation update
  • Performance improvement
  • CI/CD update

Related Issues

Closes #1307

Refs #1283 (ruled remove over enforce; not re-litigated here) · PR #1304 · #760

Changes Made

1. The block itself — 12 files, 91 lines. tabs is .optional() on ListViewSchema, so removal is schema-legal and changes no behaviour. src/views/{account,campaign,case,contact,contract,event,forecast,knowledge_article,opportunity,product,quote,task}.view.ts.

2. Five false-belief sites corrected — three named on the card, two found by the dispatching seat:

Site Was Now
test/view-references.test.ts "once a view curates a tabs array, only the listed views render", naming seven queues as "defined, tested, unreachable" Both halves were false. The block now records the measured builder and guards the direction that can still dangle — see below
src/apps/crm.app.ts header tab strip is "list.tabs in src/views/*.view.ts" the strip is built from the view descriptors; the string to edit is the target view's label
src/apps/crm.app.ts Sales group "the pipeline tab (OpportunityViews.list.tabs)" pipeline_kanban in listViews, on the strip under its own label Sales Pipeline
test/case-assignment.test.ts looked up a tabs[] entry for unassigned_triage and asserted tab.pinned === true the thing asserted is true for a different reason — the view is a listViews entry, so it is on the strip by existing. pinned was inert
test/docs-search-navigation-views.test.ts folded list.tabs[].label into the expected label set, and read isDefault off a tab entry the tab-label path was dead since #1304 (t.label permanently undefined, discarded by filter(Boolean)) and could add nothing — a tab only ever pointed at list or a listViews entry, whose labels are already in the set. Removed. "Opens on Open Deals" is now asserted as what it is: a property of being the primary list

Two further sites in the same file, not on the card, which my own deletion would otherwise have left dangling: the "All Tasks" comment cited isDefault + pinned off the tab entry, and the Activity group cited EventViews.list.tabs. Both re-authored the same way. One more in src/views/account.view.ts: the userFilters block justified its placement with "the default view's tabs act as the view switcher" — false, and the measured constraint is about element, not placement, so the comment now states that instead.

3. The #1304 pin re-aimed, not withdrawn — test/view-tab-label-inert.test.ts. It said so itself: "if a refactor empties every tabs[] array, the assertion passes vacuously". This is that refactor. Upgraded from "list.tabs[] carries no label" to "list.tabs[] is absent", and the anti-vacuity face moved with it — from "there are still tabs to inspect" to "the walk really reached the twelve list blocks it claims to have cleared", pinned by name rather than by count so a bundle dropping out of src/views/index.ts cannot silently shrink the surface.

4. account-renewal-model / decorative-field-sweep — both asserted a removed view's absence from listViews and from tabs as two distinct broken states. There is only one place a view can be reachable from, so each now asserts over the whole strip (list + listViews) — which is also stricter: reintroducing the view as the primary list would now fail too.

5. Changeset: .changeset/a-tab-is-a-view.md (hotcrm: patch).

Testing

  • Unit tests pass — pnpm test: 140 files, 2969 passed, 1 skipped (the skip is a pre-existing it.skipIf in script-main-guard.test.ts, untouched here)
  • Linting passes — pnpm lint exit 0; pnpm lint:i18n-gate prints ✓ i18n lint gate: 0 i18n/missing-* issues
  • Build succeeds — pnpm build exit 0, ✓ Build complete, 14 Views
  • New tests added
  • pnpm validate exit 0 · pnpm hygiene → ✓ source hygiene clean · pnpm hygiene:tokens → ✓ source token ratchet clean

The whole union above was re-run after the final commit, on 803742a9, with a clean tree.

Reverse verification — three legs, each with the mutation confirmed on disk by blob hash before the reading was taken, and each restored via git checkout HEAD -- <abs path> with git diff HEAD proven empty afterwards. Predicted direction was RED for all three, and all three were RED:

  1. Reintroduced a two-entry tabs block on crm_product (blob 707f529a → e830dfff) → the pin fails with crm_product.list.tabs is authored (2 entries). The anti-vacuity half stayed green, so the two halves are independent.
  2. Dropped ProductViews from src/views/index.ts (blob ee83fd09 → 52621a34) → the anti-vacuity half fails with these `list` blocks were never inspected, naming crm_product. The absence rule stayed green — which is exactly the vacuous pass it exists to catch.
  3. Renamed a nav viewName to my_open_taskz (blob f7bda5fc → de8327ab) → the new guard fails with nav_my_tasks: "crm_task" defines no view "my_open_taskz" — the entry opens on nothing, listing what the object does ship.

Additional Notes

One figure in the card does not reproduce, and it is in the title. The card says "36 of the 48 authored icons name something that can never render there". Measured on this tree, resolving every tab's view to its target view's type through the built artifact (all 48 targets resolved, none guessed):

  • 33 of 48 name an icon the target view cannot render (crown, inbox, git-commit-horizontal, user, columns-3, …)
  • 15 coincide — 8 exactly (calendar, map) and 7 by prefix (gallery-thumbnails on a gallery view, gantt-chart on a gantt one, which is the card's own example of a coincidence)

Under the stricter reading — the authored string is literally one of the eight viewTypeIcons keys — it is 39 and 9. Neither reading gives 36. The changeset and the pin carry the measured numbers together with the rule they were taken under, rather than the card's. The premise is unaffected either way: all 48 are inert, because the authored string is never looked up at all; the icon is chosen by the view's type.

The token ratchet now prints a new advisory (status 0, not a failure): removing 91 lines drops the interaction layer by ~1,027 tokens, taking its headroom to 4,576 and past the "over twice the 5% buffer" line, so the gate asks for the ceiling to be re-anchored to ~40,000. scripts/check-source-token-ratchet.mjs is outside this PR's file fence, so it is left for the maintainer rather than edited here.

Not touched, deliberately: ViewTabSchema.label and the schema field — userFilters.tabs[] (ADR-0047) is a different key on page lists where label is live and translated. src/views/lead.view.ts and src/views/event_attendee.view.ts carry no tabs. The unassigned_triage view's filter is #1145's subject and is untouched here. content/docs/** is outside the fence; the Tab columns some pages publish are reported separately to the dispatching seat.


Generated by Claude Code

claude added 2 commits August 26, 2026 05:52
The object-view switcher builds its tab strip from view descriptors --
one tab per listViews entry plus the primary list, which it moves to the
front and marks default. Re-measured on @objectstack/console 17.1.0:

  Dm({ definedViews: U.listViews, primary: U.list, primaryId,
       savedViews, viewOverrides, fallbackTab })

tabs appears nowhere in that path. A tab's text is the view's own label
and its icon is viewTypeIcons[view.type], from a map the console
hardcodes over eight view types. #1304 removed the entries' label; this
removes the rest of the entry -- 60 entries, 48 icon: keys, 12 files.

Corrects the beliefs that rested on the old model: view-references
asserted that a listViews entry left out of tabs was unreachable (it was
not -- it now guards navigation entries naming an undefined view, which
nothing checked), crm.app.ts pointed authors at list.tabs as the string
to edit, and four suites read list.tabs for a property that comes from
listViews. view-tab-label-inert is re-aimed from "carries no label" to
"tabs is absent", with an anti-vacuity half pinning that the walk really
reached the twelve list blocks.

userFilters.tabs[] (ADR-0047) is untouched: a different key on page
lists, where label is live and translated.
The card's headline reads '36 of 48'. Re-measured on this tree it is
33 of 48 that name an icon their target view cannot render, and 15 that
coincide -- 8 exactly (calendar, map) and 7 by prefix
(gallery-thumbnails on a gallery view, gantt-chart on a gantt one).
Under the stricter reading (the authored string is literally one of the
eight viewTypeIcons keys) it is 39 and 9. Neither reproduces 36, so both
the changeset and the pin now carry the measurement and the rule it was
taken under. The premise is unaffected: all 48 are inert either way.
@vercel

vercel Bot commented Aug 26, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hotcrm Ignored Ignored Aug 26, 2026 6:05am

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

2 participants