fix(app-shell): reword the app:launcher palette-exclusion reason so a renderer cannot falsify it - #7119

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7092-launcher-exclusion-reason
Sep 1, 2026
Merged

fix(app-shell): reword the app:launcher palette-exclusion reason so a renderer cannot falsify it#7119
os-warren merged 1 commit into
mainfrom
claude/issue-7092-launcher-exclusion-reason

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7092

One line of source. PALETTE_EXCLUSIONS['app:launcher'] read 'shell singleton — the app shell renders it, not a page'. PR #7091 landed on main as 969ba84f4, registering a real app:launcher renderer, so a page can now render it and the "not a page" clause is false today.

- 'app:launcher': 'shell singleton — the app shell renders it, not a page',+ 'app:launcher': 'shell singleton — lives in the app shell chrome',

The wording is the defect, not the decision. app:launcher stays out of the Studio page palette; the entry, its key and its position are unchanged. Whether it should become palette-authorable is a live product question, and moving the entry rather than rewording it would have answered that silently — so the entry did not move. Direct precedent: #6071 corrected two reasons on this same ledger in place.

The new text mirrors the sibling 'global:notifications': 'shell singleton — lives in the app shell header', which carries the identical shell-singleton marker and did not rot when #6757 shipped it a real renderer — because it describes where the thing lives, not whether a renderer exists. #7091's own docblock reaches the same conclusion independently:

This does NOT put the block in the Studio page palette: PALETTE_EXCLUSIONS still records app:launcher as a shell singleton, and that is a palette decision about authoring ergonomics, independent of whether a declared type renders — exactly as objectui#6757 left global:notifications.

Measurements

All runs below are on ab85b515e with a clean tree.

GateExitVerdict
vitest packages/app-shell/.../previews/__tests__/0green — 7 files, 73 tests passed
check-changeset-presence.mjs0green
check-changeset-no-major.mjs0green
check-changeset-overwrite.mjs0green
check-changeset-fixed.mjs0green
check:control-bytes0green — 5893 tracked text files
check:doc-fences0green
pnpm lint (full farm, plain form)0green — 47/47 tasks, 0 errors
pnpm --filter @object-ui/app-shell run type-check0green — after building the closure

Exit codes were captured by redirecting to a file first, never read across a pipe.

type-check was NOT MEASURED on the first attempt and is recorded as such rather than as a failure: it exited 2 with TS2307: Cannot find module '@object-ui/components' (and @object-ui/react, @object-ui/fields, @object-ui/data-objectstack) — an unbuilt dependency closure. After pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build (exit 0) it re-ran at exit 0 with 0 remaining TS2307. Both passes run (tsc --noEmit && tsc -p tsconfig.test.json), so test sources are in the population too.

Population proof for the typecheck: tsc --listFiles lists 2421 files and previews/block-types.ts is among them (1 hit), so the green covers the edited file rather than merely omitting it.

The truthfulness test: what it actually judges

exclusion-reason-truthfulness.test.ts judges only reasons matching /\bno\s+(?:\w+\s+){0,2}renderer\b/i. Neither the old text (which says "renders", not "renderer") nor the new text matches, so this entry was never in that guard's population — it stays green, and its non-vacuity assertion is still satisfied by ai:chat_window and element:form. All 4 of its assertions pass, including both anti-vacuity guards.

Reverse verification

Predicted directions were fixed before running. Each leg confirmed the mutation on disk (injected/removed text counts plus a blob-hash move off the HEAD blob) and verified the restore by hash match plus an empty git diff HEAD.

Changeset

Empty frontmatter, following #6071's changeset on this same ledger. The presence gate certifies the route explicitly:

Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption and a complete answer to this gate.

That matches the measurement: PALETTE_EXCLUSIONS is not exported from @object-ui/app-shell's entry (the package exposes only . and ./styles.css), nothing re-exports block-types anywhere in app-shell src, and no runtime code path reads the reason strings — they are developer-facing ledger prose. This corrects the dispatch's assumption that a patch was owed.

Swept and deliberately left


Generated by Claude Code

… a renderer cannot falsify it
`PALETTE_EXCLUSIONS['app:launcher']` read "shell singleton — the app shell
renders it, not a page". objectui#7091 registered a real `app:launcher`
renderer, so a page CAN now render it and the "not a page" clause is false on
`main`.
The wording is the defect, not the decision. `app:launcher` stays out of the
Studio page palette; the entry, its key and its position are unchanged. The new
text mirrors the sibling `global:notifications`, which carries the identical
shell-singleton marker and did NOT rot when objectui#6757 shipped it a real
renderer — because it describes WHERE the thing lives rather than whether a
renderer exists.
Whether `app:launcher` should become palette-authorable is a separate product
question; moving the entry rather than rewording it would have answered that
silently, so the entry did not move.
`exclusion-reason-truthfulness.test.ts` judges only reasons matching
/\bno\s+(?:\w+\s+){0,2}renderer\b/i; neither the old nor the new text matches, so
this entry was never in that guard's population and the guard's non-vacuity
assertion is still satisfied by `ai:chat_window` and `element:form`.
Changeset carries empty frontmatter, following objectui#6071 on this same
ledger: the reason strings are developer-facing prose read by no runtime code
path, and `PALETTE_EXCLUSIONS` is not exported from the package entry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3151.4 KB3191.4 KB
Main entry chunk (gzip)142.3 KB350 KB
Entry fileindex-LPKhNev8.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)248.94KB63.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.31KB54.66KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.19KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ ACCEPT (on the substance) — PM seat (domain:ui), reviewer of record

Landing not yet armed — test shards still in_progress at ab85b515e. in_progress is not a pass. Conflict pre-check will run against main before I flip ready; behind by itself is not an action item, since the merge queue rebuilds each entry on current main.

The diff is one source line, which is what the card asked for and what a lot of discipline went into not exceeding.

⭐⭐ You caught a real error in my dispatch order, and I have verified it

My ZONE 1 cited PR #7091 as "merged 48d1beee1, 2026-08-31T20:38:36Z". Re-measured just now:

sharesolves in this repo?
48d1beee1 — what I wrote for #7091unknown revision
1c91abd98 — what I wrote for #6961 on #6969's orderunknown revision
03380aa14 / 5f4514f7b⚠️control — read from git log origin/main✅ both resolve

You are exactly right: those are pre-squash PR head shas. I read them from the PR object's head.sha and wrote them as if they were the landed commit. A squash merge creates a new commit, and #7091 landed as 969ba84f4.

⚠️Why this is worse than a typo, and why I am recording it loudly: the citation was offered as the evidence that the card's precondition was met. A future agent running git show 48d1beee1 gets "unknown revision" and has every reason to conclude the premise is unverifiable — turning a ripe card back into a doubtful one. A verification artifact that cannot be re-run is not a verification artifact.

⭐ And your response was the correct one: you did not stop, and you did not take my word. You re-derived the premise by content969ba84f4 adds app-launcher-renderer.tsx and nav-menu-renderer.tsx and registers both in app-shell/src/index.ts. Content survives a squash; a sha does not. That is the more durable instrument and it is going into the seat's standing lessons.

⭐ The changeset assumption was wrong again — in the opposite direction from three hours ago

I said patch. The answer is EMPTY frontmatter, and you did not merely assert it — you measured the property that decides it (PALETTE_EXCLUSIONS is not exported from the package entry, which exposes only . and ./styles.css; nothing re-exports block-types in app-shell src; no runtime path reads the reason strings) and then found the direct precedent, #6071's own changeset on this same ledger, with the same rationale.

⚠️ For the record: on #7015 this morning I told a dev an empty-frontmatter changeset was owed when the gate said none was owed at all. Here I told you patch when empty frontmatter was owed. Two wrong predictions, in opposite directions, in one session. The conclusion is not a better rule — it is that I should stop predicting this gate and only ever say "quote its verdict line." That is now the standing instruction.

⭐⭐ Leg B is the most valuable thing in this PR, and it is not the PR

You predicted GREEN and got GREEN — then asked why, and found the guard is blind: setting the reason to 'no renderer ZZMUTZZ', which does match CLAIMS_NO_RENDERER, still passes 4/4, because the test's import set never grew to include app-shell (which now registers four page blocks via #6757 and #7091).

⇒ A guard that would pass a genuinely false claim it was written to catch. Filed as #7117 at the right confidence level — latent, not live, with the reason stated (no shell singleton currently makes that claim, and this reword keeps it that way).

⭐ This is the same shape as the card you were fixing, one level up: #7092 was a comment that certified adjacent code; #7117 is a test that certifies a claim it cannot see. And it was found only because the ablation reported its green as information rather than as reassurance — the exact discipline #6969's card is about.

The self-aborted ablation leg — reported as NOT RUN, correctly

An earlier leg self-aborted on FAILURE: mutation did NOT land because grep -c counts lines and the single-char marker x matched 66 of them. Reporting that as not run rather than quietly retrying is the fourth instance today on this lane of the same class: an instrument that fails while still printing something plausible (rg -ril silently rewriting its output on #7015; grep -c -F with an embedded newline matching every line on #6969; my own cd-reset sweep printing "queue drained" from an echo). Four independent hits in one session is not coincidence — it is now a named class in the seat post.

The sweep, and what it declined to fix

nav:menu#7091's other renderer — is in BLOCK_TYPE_META, not PALETTE_EXCLUSIONS, so it falsified no reason (0 hits in the exclusion range, control app:launcher = 1 in the same query). And declining the BlockTypeId docblock is the judgment I would have made: it claims membership of a local union, which is still true, and it covers global:notifications identically — which #6757 deliberately left. Rewording it would have applied a standard the card's own cited precedent rejects.

Every zero in the report carries a control that must hit in the same query. That is the standard.


Re-checking CI; I arm on green.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 02:56
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 44ea62dSep 1, 2026
32 checks passed
@os-warren
os-warren deleted the claude/issue-7092-launcher-exclusion-reason branch September 1, 2026 03:10
Sign up for freeto 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.

finding(app-shell): PALETTE_EXCLUSIONS['app:launcher'] says "the app shell renders it, not a page" — PR #7091 makes that clause false

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(app-shell): reword the app:launcher palette-exclusion reason so a renderer cannot falsify it - #7119

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7092-launcher-exclusion-reason
Sep 1, 2026
Merged

fix(app-shell): reword the app:launcher palette-exclusion reason so a renderer cannot falsify it#7119
os-warren merged 1 commit into
mainfrom
claude/issue-7092-launcher-exclusion-reason

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7092

One line of source. PALETTE_EXCLUSIONS['app:launcher'] read 'shell singleton — the app shell renders it, not a page'. PR #7091 landed on main as 969ba84f4, registering a real app:launcher renderer, so a page can now render it and the "not a page" clause is false today.

- 'app:launcher': 'shell singleton — the app shell renders it, not a page',+ 'app:launcher': 'shell singleton — lives in the app shell chrome',

The wording is the defect, not the decision. app:launcher stays out of the Studio page palette; the entry, its key and its position are unchanged. Whether it should become palette-authorable is a live product question, and moving the entry rather than rewording it would have answered that silently — so the entry did not move. Direct precedent: #6071 corrected two reasons on this same ledger in place.

The new text mirrors the sibling 'global:notifications': 'shell singleton — lives in the app shell header', which carries the identical shell-singleton marker and did not rot when #6757 shipped it a real renderer — because it describes where the thing lives, not whether a renderer exists. #7091's own docblock reaches the same conclusion independently:

This does NOT put the block in the Studio page palette: PALETTE_EXCLUSIONS still records app:launcher as a shell singleton, and that is a palette decision about authoring ergonomics, independent of whether a declared type renders — exactly as objectui#6757 left global:notifications.

Measurements

All runs below are on ab85b515e with a clean tree.

GateExitVerdict
vitest packages/app-shell/.../previews/__tests__/0green — 7 files, 73 tests passed
check-changeset-presence.mjs0green
check-changeset-no-major.mjs0green
check-changeset-overwrite.mjs0green
check-changeset-fixed.mjs0green
check:control-bytes0green — 5893 tracked text files
check:doc-fences0green
pnpm lint (full farm, plain form)0green — 47/47 tasks, 0 errors
pnpm --filter @object-ui/app-shell run type-check0green — after building the closure

Exit codes were captured by redirecting to a file first, never read across a pipe.

type-check was NOT MEASURED on the first attempt and is recorded as such rather than as a failure: it exited 2 with TS2307: Cannot find module '@object-ui/components' (and @object-ui/react, @object-ui/fields, @object-ui/data-objectstack) — an unbuilt dependency closure. After pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build (exit 0) it re-ran at exit 0 with 0 remaining TS2307. Both passes run (tsc --noEmit && tsc -p tsconfig.test.json), so test sources are in the population too.

Population proof for the typecheck: tsc --listFiles lists 2421 files and previews/block-types.ts is among them (1 hit), so the green covers the edited file rather than merely omitting it.

The truthfulness test: what it actually judges

exclusion-reason-truthfulness.test.ts judges only reasons matching /\bno\s+(?:\w+\s+){0,2}renderer\b/i. Neither the old text (which says "renders", not "renderer") nor the new text matches, so this entry was never in that guard's population — it stays green, and its non-vacuity assertion is still satisfied by ai:chat_window and element:form. All 4 of its assertions pass, including both anti-vacuity guards.

Reverse verification

Predicted directions were fixed before running. Each leg confirmed the mutation on disk (injected/removed text counts plus a blob-hash move off the HEAD blob) and verified the restore by hash match plus an empty git diff HEAD.

Changeset

Empty frontmatter, following #6071's changeset on this same ledger. The presence gate certifies the route explicitly:

Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption and a complete answer to this gate.

That matches the measurement: PALETTE_EXCLUSIONS is not exported from @object-ui/app-shell's entry (the package exposes only . and ./styles.css), nothing re-exports block-types anywhere in app-shell src, and no runtime code path reads the reason strings — they are developer-facing ledger prose. This corrects the dispatch's assumption that a patch was owed.

Swept and deliberately left


Generated by Claude Code

… a renderer cannot falsify it
`PALETTE_EXCLUSIONS['app:launcher']` read "shell singleton — the app shell
renders it, not a page". objectui#7091 registered a real `app:launcher`
renderer, so a page CAN now render it and the "not a page" clause is false on
`main`.
The wording is the defect, not the decision. `app:launcher` stays out of the
Studio page palette; the entry, its key and its position are unchanged. The new
text mirrors the sibling `global:notifications`, which carries the identical
shell-singleton marker and did NOT rot when objectui#6757 shipped it a real
renderer — because it describes WHERE the thing lives rather than whether a
renderer exists.
Whether `app:launcher` should become palette-authorable is a separate product
question; moving the entry rather than rewording it would have answered that
silently, so the entry did not move.
`exclusion-reason-truthfulness.test.ts` judges only reasons matching
/\bno\s+(?:\w+\s+){0,2}renderer\b/i; neither the old nor the new text matches, so
this entry was never in that guard's population and the guard's non-vacuity
assertion is still satisfied by `ai:chat_window` and `element:form`.
Changeset carries empty frontmatter, following objectui#6071 on this same
ledger: the reason strings are developer-facing prose read by no runtime code
path, and `PALETTE_EXCLUSIONS` is not exported from the package entry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3151.4 KB3191.4 KB
Main entry chunk (gzip)142.3 KB350 KB
Entry fileindex-LPKhNev8.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)248.94KB63.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.31KB54.66KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.19KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ ACCEPT (on the substance) — PM seat (domain:ui), reviewer of record

Landing not yet armed — test shards still in_progress at ab85b515e. in_progress is not a pass. Conflict pre-check will run against main before I flip ready; behind by itself is not an action item, since the merge queue rebuilds each entry on current main.

The diff is one source line, which is what the card asked for and what a lot of discipline went into not exceeding.

⭐⭐ You caught a real error in my dispatch order, and I have verified it

My ZONE 1 cited PR #7091 as "merged 48d1beee1, 2026-08-31T20:38:36Z". Re-measured just now:

sharesolves in this repo?
48d1beee1 — what I wrote for #7091unknown revision
1c91abd98 — what I wrote for #6961 on #6969's orderunknown revision
03380aa14 / 5f4514f7b⚠️control — read from git log origin/main✅ both resolve

You are exactly right: those are pre-squash PR head shas. I read them from the PR object's head.sha and wrote them as if they were the landed commit. A squash merge creates a new commit, and #7091 landed as 969ba84f4.

⚠️Why this is worse than a typo, and why I am recording it loudly: the citation was offered as the evidence that the card's precondition was met. A future agent running git show 48d1beee1 gets "unknown revision" and has every reason to conclude the premise is unverifiable — turning a ripe card back into a doubtful one. A verification artifact that cannot be re-run is not a verification artifact.

⭐ And your response was the correct one: you did not stop, and you did not take my word. You re-derived the premise by content969ba84f4 adds app-launcher-renderer.tsx and nav-menu-renderer.tsx and registers both in app-shell/src/index.ts. Content survives a squash; a sha does not. That is the more durable instrument and it is going into the seat's standing lessons.

⭐ The changeset assumption was wrong again — in the opposite direction from three hours ago

I said patch. The answer is EMPTY frontmatter, and you did not merely assert it — you measured the property that decides it (PALETTE_EXCLUSIONS is not exported from the package entry, which exposes only . and ./styles.css; nothing re-exports block-types in app-shell src; no runtime path reads the reason strings) and then found the direct precedent, #6071's own changeset on this same ledger, with the same rationale.

⚠️ For the record: on #7015 this morning I told a dev an empty-frontmatter changeset was owed when the gate said none was owed at all. Here I told you patch when empty frontmatter was owed. Two wrong predictions, in opposite directions, in one session. The conclusion is not a better rule — it is that I should stop predicting this gate and only ever say "quote its verdict line." That is now the standing instruction.

⭐⭐ Leg B is the most valuable thing in this PR, and it is not the PR

You predicted GREEN and got GREEN — then asked why, and found the guard is blind: setting the reason to 'no renderer ZZMUTZZ', which does match CLAIMS_NO_RENDERER, still passes 4/4, because the test's import set never grew to include app-shell (which now registers four page blocks via #6757 and #7091).

⇒ A guard that would pass a genuinely false claim it was written to catch. Filed as #7117 at the right confidence level — latent, not live, with the reason stated (no shell singleton currently makes that claim, and this reword keeps it that way).

⭐ This is the same shape as the card you were fixing, one level up: #7092 was a comment that certified adjacent code; #7117 is a test that certifies a claim it cannot see. And it was found only because the ablation reported its green as information rather than as reassurance — the exact discipline #6969's card is about.

The self-aborted ablation leg — reported as NOT RUN, correctly

An earlier leg self-aborted on FAILURE: mutation did NOT land because grep -c counts lines and the single-char marker x matched 66 of them. Reporting that as not run rather than quietly retrying is the fourth instance today on this lane of the same class: an instrument that fails while still printing something plausible (rg -ril silently rewriting its output on #7015; grep -c -F with an embedded newline matching every line on #6969; my own cd-reset sweep printing "queue drained" from an echo). Four independent hits in one session is not coincidence — it is now a named class in the seat post.

The sweep, and what it declined to fix

nav:menu#7091's other renderer — is in BLOCK_TYPE_META, not PALETTE_EXCLUSIONS, so it falsified no reason (0 hits in the exclusion range, control app:launcher = 1 in the same query). And declining the BlockTypeId docblock is the judgment I would have made: it claims membership of a local union, which is still true, and it covers global:notifications identically — which #6757 deliberately left. Rewording it would have applied a standard the card's own cited precedent rejects.

Every zero in the report carries a control that must hit in the same query. That is the standard.


Re-checking CI; I arm on green.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 02:56
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 44ea62dSep 1, 2026
32 checks passed
@os-warren
os-warren deleted the claude/issue-7092-launcher-exclusion-reason branch September 1, 2026 03:10
Sign up for freeto 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.

finding(app-shell): PALETTE_EXCLUSIONS['app:launcher'] says "the app shell renders it, not a page" — PR #7091 makes that clause false

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(app-shell): reword the app:launcher palette-exclusion reason so a renderer cannot falsify it - #7119

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7092-launcher-exclusion-reason
Sep 1, 2026
Merged

fix(app-shell): reword the app:launcher palette-exclusion reason so a renderer cannot falsify it#7119
os-warren merged 1 commit into
mainfrom
claude/issue-7092-launcher-exclusion-reason

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7092

One line of source. PALETTE_EXCLUSIONS['app:launcher'] read 'shell singleton — the app shell renders it, not a page'. PR #7091 landed on main as 969ba84f4, registering a real app:launcher renderer, so a page can now render it and the "not a page" clause is false today.

- 'app:launcher': 'shell singleton — the app shell renders it, not a page',+ 'app:launcher': 'shell singleton — lives in the app shell chrome',

The wording is the defect, not the decision. app:launcher stays out of the Studio page palette; the entry, its key and its position are unchanged. Whether it should become palette-authorable is a live product question, and moving the entry rather than rewording it would have answered that silently — so the entry did not move. Direct precedent: #6071 corrected two reasons on this same ledger in place.

The new text mirrors the sibling 'global:notifications': 'shell singleton — lives in the app shell header', which carries the identical shell-singleton marker and did not rot when #6757 shipped it a real renderer — because it describes where the thing lives, not whether a renderer exists. #7091's own docblock reaches the same conclusion independently:

This does NOT put the block in the Studio page palette: PALETTE_EXCLUSIONS still records app:launcher as a shell singleton, and that is a palette decision about authoring ergonomics, independent of whether a declared type renders — exactly as objectui#6757 left global:notifications.

Measurements

All runs below are on ab85b515e with a clean tree.

GateExitVerdict
vitest packages/app-shell/.../previews/__tests__/0green — 7 files, 73 tests passed
check-changeset-presence.mjs0green
check-changeset-no-major.mjs0green
check-changeset-overwrite.mjs0green
check-changeset-fixed.mjs0green
check:control-bytes0green — 5893 tracked text files
check:doc-fences0green
pnpm lint (full farm, plain form)0green — 47/47 tasks, 0 errors
pnpm --filter @object-ui/app-shell run type-check0green — after building the closure

Exit codes were captured by redirecting to a file first, never read across a pipe.

type-check was NOT MEASURED on the first attempt and is recorded as such rather than as a failure: it exited 2 with TS2307: Cannot find module '@object-ui/components' (and @object-ui/react, @object-ui/fields, @object-ui/data-objectstack) — an unbuilt dependency closure. After pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build (exit 0) it re-ran at exit 0 with 0 remaining TS2307. Both passes run (tsc --noEmit && tsc -p tsconfig.test.json), so test sources are in the population too.

Population proof for the typecheck: tsc --listFiles lists 2421 files and previews/block-types.ts is among them (1 hit), so the green covers the edited file rather than merely omitting it.

The truthfulness test: what it actually judges

exclusion-reason-truthfulness.test.ts judges only reasons matching /\bno\s+(?:\w+\s+){0,2}renderer\b/i. Neither the old text (which says "renders", not "renderer") nor the new text matches, so this entry was never in that guard's population — it stays green, and its non-vacuity assertion is still satisfied by ai:chat_window and element:form. All 4 of its assertions pass, including both anti-vacuity guards.

Reverse verification

Predicted directions were fixed before running. Each leg confirmed the mutation on disk (injected/removed text counts plus a blob-hash move off the HEAD blob) and verified the restore by hash match plus an empty git diff HEAD.

Changeset

Empty frontmatter, following #6071's changeset on this same ledger. The presence gate certifies the route explicitly:

Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption and a complete answer to this gate.

That matches the measurement: PALETTE_EXCLUSIONS is not exported from @object-ui/app-shell's entry (the package exposes only . and ./styles.css), nothing re-exports block-types anywhere in app-shell src, and no runtime code path reads the reason strings — they are developer-facing ledger prose. This corrects the dispatch's assumption that a patch was owed.

Swept and deliberately left


Generated by Claude Code

… a renderer cannot falsify it
`PALETTE_EXCLUSIONS['app:launcher']` read "shell singleton — the app shell
renders it, not a page". objectui#7091 registered a real `app:launcher`
renderer, so a page CAN now render it and the "not a page" clause is false on
`main`.
The wording is the defect, not the decision. `app:launcher` stays out of the
Studio page palette; the entry, its key and its position are unchanged. The new
text mirrors the sibling `global:notifications`, which carries the identical
shell-singleton marker and did NOT rot when objectui#6757 shipped it a real
renderer — because it describes WHERE the thing lives rather than whether a
renderer exists.
Whether `app:launcher` should become palette-authorable is a separate product
question; moving the entry rather than rewording it would have answered that
silently, so the entry did not move.
`exclusion-reason-truthfulness.test.ts` judges only reasons matching
/\bno\s+(?:\w+\s+){0,2}renderer\b/i; neither the old nor the new text matches, so
this entry was never in that guard's population and the guard's non-vacuity
assertion is still satisfied by `ai:chat_window` and `element:form`.
Changeset carries empty frontmatter, following objectui#6071 on this same
ledger: the reason strings are developer-facing prose read by no runtime code
path, and `PALETTE_EXCLUSIONS` is not exported from the package entry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3151.4 KB3191.4 KB
Main entry chunk (gzip)142.3 KB350 KB
Entry fileindex-LPKhNev8.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)248.94KB63.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.31KB54.66KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.19KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ ACCEPT (on the substance) — PM seat (domain:ui), reviewer of record

Landing not yet armed — test shards still in_progress at ab85b515e. in_progress is not a pass. Conflict pre-check will run against main before I flip ready; behind by itself is not an action item, since the merge queue rebuilds each entry on current main.

The diff is one source line, which is what the card asked for and what a lot of discipline went into not exceeding.

⭐⭐ You caught a real error in my dispatch order, and I have verified it

My ZONE 1 cited PR #7091 as "merged 48d1beee1, 2026-08-31T20:38:36Z". Re-measured just now:

sharesolves in this repo?
48d1beee1 — what I wrote for #7091unknown revision
1c91abd98 — what I wrote for #6961 on #6969's orderunknown revision
03380aa14 / 5f4514f7b⚠️control — read from git log origin/main✅ both resolve

You are exactly right: those are pre-squash PR head shas. I read them from the PR object's head.sha and wrote them as if they were the landed commit. A squash merge creates a new commit, and #7091 landed as 969ba84f4.

⚠️Why this is worse than a typo, and why I am recording it loudly: the citation was offered as the evidence that the card's precondition was met. A future agent running git show 48d1beee1 gets "unknown revision" and has every reason to conclude the premise is unverifiable — turning a ripe card back into a doubtful one. A verification artifact that cannot be re-run is not a verification artifact.

⭐ And your response was the correct one: you did not stop, and you did not take my word. You re-derived the premise by content969ba84f4 adds app-launcher-renderer.tsx and nav-menu-renderer.tsx and registers both in app-shell/src/index.ts. Content survives a squash; a sha does not. That is the more durable instrument and it is going into the seat's standing lessons.

⭐ The changeset assumption was wrong again — in the opposite direction from three hours ago

I said patch. The answer is EMPTY frontmatter, and you did not merely assert it — you measured the property that decides it (PALETTE_EXCLUSIONS is not exported from the package entry, which exposes only . and ./styles.css; nothing re-exports block-types in app-shell src; no runtime path reads the reason strings) and then found the direct precedent, #6071's own changeset on this same ledger, with the same rationale.

⚠️ For the record: on #7015 this morning I told a dev an empty-frontmatter changeset was owed when the gate said none was owed at all. Here I told you patch when empty frontmatter was owed. Two wrong predictions, in opposite directions, in one session. The conclusion is not a better rule — it is that I should stop predicting this gate and only ever say "quote its verdict line." That is now the standing instruction.

⭐⭐ Leg B is the most valuable thing in this PR, and it is not the PR

You predicted GREEN and got GREEN — then asked why, and found the guard is blind: setting the reason to 'no renderer ZZMUTZZ', which does match CLAIMS_NO_RENDERER, still passes 4/4, because the test's import set never grew to include app-shell (which now registers four page blocks via #6757 and #7091).

⇒ A guard that would pass a genuinely false claim it was written to catch. Filed as #7117 at the right confidence level — latent, not live, with the reason stated (no shell singleton currently makes that claim, and this reword keeps it that way).

⭐ This is the same shape as the card you were fixing, one level up: #7092 was a comment that certified adjacent code; #7117 is a test that certifies a claim it cannot see. And it was found only because the ablation reported its green as information rather than as reassurance — the exact discipline #6969's card is about.

The self-aborted ablation leg — reported as NOT RUN, correctly

An earlier leg self-aborted on FAILURE: mutation did NOT land because grep -c counts lines and the single-char marker x matched 66 of them. Reporting that as not run rather than quietly retrying is the fourth instance today on this lane of the same class: an instrument that fails while still printing something plausible (rg -ril silently rewriting its output on #7015; grep -c -F with an embedded newline matching every line on #6969; my own cd-reset sweep printing "queue drained" from an echo). Four independent hits in one session is not coincidence — it is now a named class in the seat post.

The sweep, and what it declined to fix

nav:menu#7091's other renderer — is in BLOCK_TYPE_META, not PALETTE_EXCLUSIONS, so it falsified no reason (0 hits in the exclusion range, control app:launcher = 1 in the same query). And declining the BlockTypeId docblock is the judgment I would have made: it claims membership of a local union, which is still true, and it covers global:notifications identically — which #6757 deliberately left. Rewording it would have applied a standard the card's own cited precedent rejects.

Every zero in the report carries a control that must hit in the same query. That is the standard.


Re-checking CI; I arm on green.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 02:56
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 44ea62dSep 1, 2026
32 checks passed
@os-warren
os-warren deleted the claude/issue-7092-launcher-exclusion-reason branch September 1, 2026 03:10
Sign up for freeto 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.

finding(app-shell): PALETTE_EXCLUSIONS['app:launcher'] says "the app shell renders it, not a page" — PR #7091 makes that clause false

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(app-shell): reword the app:launcher palette-exclusion reason so a renderer cannot falsify it - #7119

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7092-launcher-exclusion-reason
Sep 1, 2026
Merged

fix(app-shell): reword the app:launcher palette-exclusion reason so a renderer cannot falsify it#7119
os-warren merged 1 commit into
mainfrom
claude/issue-7092-launcher-exclusion-reason

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7092

One line of source. PALETTE_EXCLUSIONS['app:launcher'] read 'shell singleton — the app shell renders it, not a page'. PR #7091 landed on main as 969ba84f4, registering a real app:launcher renderer, so a page can now render it and the "not a page" clause is false today.

- 'app:launcher': 'shell singleton — the app shell renders it, not a page',+ 'app:launcher': 'shell singleton — lives in the app shell chrome',

The wording is the defect, not the decision. app:launcher stays out of the Studio page palette; the entry, its key and its position are unchanged. Whether it should become palette-authorable is a live product question, and moving the entry rather than rewording it would have answered that silently — so the entry did not move. Direct precedent: #6071 corrected two reasons on this same ledger in place.

The new text mirrors the sibling 'global:notifications': 'shell singleton — lives in the app shell header', which carries the identical shell-singleton marker and did not rot when #6757 shipped it a real renderer — because it describes where the thing lives, not whether a renderer exists. #7091's own docblock reaches the same conclusion independently:

This does NOT put the block in the Studio page palette: PALETTE_EXCLUSIONS still records app:launcher as a shell singleton, and that is a palette decision about authoring ergonomics, independent of whether a declared type renders — exactly as objectui#6757 left global:notifications.

Measurements

All runs below are on ab85b515e with a clean tree.

GateExitVerdict
vitest packages/app-shell/.../previews/__tests__/0green — 7 files, 73 tests passed
check-changeset-presence.mjs0green
check-changeset-no-major.mjs0green
check-changeset-overwrite.mjs0green
check-changeset-fixed.mjs0green
check:control-bytes0green — 5893 tracked text files
check:doc-fences0green
pnpm lint (full farm, plain form)0green — 47/47 tasks, 0 errors
pnpm --filter @object-ui/app-shell run type-check0green — after building the closure

Exit codes were captured by redirecting to a file first, never read across a pipe.

type-check was NOT MEASURED on the first attempt and is recorded as such rather than as a failure: it exited 2 with TS2307: Cannot find module '@object-ui/components' (and @object-ui/react, @object-ui/fields, @object-ui/data-objectstack) — an unbuilt dependency closure. After pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build (exit 0) it re-ran at exit 0 with 0 remaining TS2307. Both passes run (tsc --noEmit && tsc -p tsconfig.test.json), so test sources are in the population too.

Population proof for the typecheck: tsc --listFiles lists 2421 files and previews/block-types.ts is among them (1 hit), so the green covers the edited file rather than merely omitting it.

The truthfulness test: what it actually judges

exclusion-reason-truthfulness.test.ts judges only reasons matching /\bno\s+(?:\w+\s+){0,2}renderer\b/i. Neither the old text (which says "renders", not "renderer") nor the new text matches, so this entry was never in that guard's population — it stays green, and its non-vacuity assertion is still satisfied by ai:chat_window and element:form. All 4 of its assertions pass, including both anti-vacuity guards.

Reverse verification

Predicted directions were fixed before running. Each leg confirmed the mutation on disk (injected/removed text counts plus a blob-hash move off the HEAD blob) and verified the restore by hash match plus an empty git diff HEAD.

Changeset

Empty frontmatter, following #6071's changeset on this same ledger. The presence gate certifies the route explicitly:

Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption and a complete answer to this gate.

That matches the measurement: PALETTE_EXCLUSIONS is not exported from @object-ui/app-shell's entry (the package exposes only . and ./styles.css), nothing re-exports block-types anywhere in app-shell src, and no runtime code path reads the reason strings — they are developer-facing ledger prose. This corrects the dispatch's assumption that a patch was owed.

Swept and deliberately left


Generated by Claude Code

… a renderer cannot falsify it
`PALETTE_EXCLUSIONS['app:launcher']` read "shell singleton — the app shell
renders it, not a page". objectui#7091 registered a real `app:launcher`
renderer, so a page CAN now render it and the "not a page" clause is false on
`main`.
The wording is the defect, not the decision. `app:launcher` stays out of the
Studio page palette; the entry, its key and its position are unchanged. The new
text mirrors the sibling `global:notifications`, which carries the identical
shell-singleton marker and did NOT rot when objectui#6757 shipped it a real
renderer — because it describes WHERE the thing lives rather than whether a
renderer exists.
Whether `app:launcher` should become palette-authorable is a separate product
question; moving the entry rather than rewording it would have answered that
silently, so the entry did not move.
`exclusion-reason-truthfulness.test.ts` judges only reasons matching
/\bno\s+(?:\w+\s+){0,2}renderer\b/i; neither the old nor the new text matches, so
this entry was never in that guard's population and the guard's non-vacuity
assertion is still satisfied by `ai:chat_window` and `element:form`.
Changeset carries empty frontmatter, following objectui#6071 on this same
ledger: the reason strings are developer-facing prose read by no runtime code
path, and `PALETTE_EXCLUSIONS` is not exported from the package entry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3151.4 KB3191.4 KB
Main entry chunk (gzip)142.3 KB350 KB
Entry fileindex-LPKhNev8.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)248.94KB63.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.31KB54.66KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.19KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ ACCEPT (on the substance) — PM seat (domain:ui), reviewer of record

Landing not yet armed — test shards still in_progress at ab85b515e. in_progress is not a pass. Conflict pre-check will run against main before I flip ready; behind by itself is not an action item, since the merge queue rebuilds each entry on current main.

The diff is one source line, which is what the card asked for and what a lot of discipline went into not exceeding.

⭐⭐ You caught a real error in my dispatch order, and I have verified it

My ZONE 1 cited PR #7091 as "merged 48d1beee1, 2026-08-31T20:38:36Z". Re-measured just now:

sharesolves in this repo?
48d1beee1 — what I wrote for #7091unknown revision
1c91abd98 — what I wrote for #6961 on #6969's orderunknown revision
03380aa14 / 5f4514f7b⚠️control — read from git log origin/main✅ both resolve

You are exactly right: those are pre-squash PR head shas. I read them from the PR object's head.sha and wrote them as if they were the landed commit. A squash merge creates a new commit, and #7091 landed as 969ba84f4.

⚠️Why this is worse than a typo, and why I am recording it loudly: the citation was offered as the evidence that the card's precondition was met. A future agent running git show 48d1beee1 gets "unknown revision" and has every reason to conclude the premise is unverifiable — turning a ripe card back into a doubtful one. A verification artifact that cannot be re-run is not a verification artifact.

⭐ And your response was the correct one: you did not stop, and you did not take my word. You re-derived the premise by content969ba84f4 adds app-launcher-renderer.tsx and nav-menu-renderer.tsx and registers both in app-shell/src/index.ts. Content survives a squash; a sha does not. That is the more durable instrument and it is going into the seat's standing lessons.

⭐ The changeset assumption was wrong again — in the opposite direction from three hours ago

I said patch. The answer is EMPTY frontmatter, and you did not merely assert it — you measured the property that decides it (PALETTE_EXCLUSIONS is not exported from the package entry, which exposes only . and ./styles.css; nothing re-exports block-types in app-shell src; no runtime path reads the reason strings) and then found the direct precedent, #6071's own changeset on this same ledger, with the same rationale.

⚠️ For the record: on #7015 this morning I told a dev an empty-frontmatter changeset was owed when the gate said none was owed at all. Here I told you patch when empty frontmatter was owed. Two wrong predictions, in opposite directions, in one session. The conclusion is not a better rule — it is that I should stop predicting this gate and only ever say "quote its verdict line." That is now the standing instruction.

⭐⭐ Leg B is the most valuable thing in this PR, and it is not the PR

You predicted GREEN and got GREEN — then asked why, and found the guard is blind: setting the reason to 'no renderer ZZMUTZZ', which does match CLAIMS_NO_RENDERER, still passes 4/4, because the test's import set never grew to include app-shell (which now registers four page blocks via #6757 and #7091).

⇒ A guard that would pass a genuinely false claim it was written to catch. Filed as #7117 at the right confidence level — latent, not live, with the reason stated (no shell singleton currently makes that claim, and this reword keeps it that way).

⭐ This is the same shape as the card you were fixing, one level up: #7092 was a comment that certified adjacent code; #7117 is a test that certifies a claim it cannot see. And it was found only because the ablation reported its green as information rather than as reassurance — the exact discipline #6969's card is about.

The self-aborted ablation leg — reported as NOT RUN, correctly

An earlier leg self-aborted on FAILURE: mutation did NOT land because grep -c counts lines and the single-char marker x matched 66 of them. Reporting that as not run rather than quietly retrying is the fourth instance today on this lane of the same class: an instrument that fails while still printing something plausible (rg -ril silently rewriting its output on #7015; grep -c -F with an embedded newline matching every line on #6969; my own cd-reset sweep printing "queue drained" from an echo). Four independent hits in one session is not coincidence — it is now a named class in the seat post.

The sweep, and what it declined to fix

nav:menu#7091's other renderer — is in BLOCK_TYPE_META, not PALETTE_EXCLUSIONS, so it falsified no reason (0 hits in the exclusion range, control app:launcher = 1 in the same query). And declining the BlockTypeId docblock is the judgment I would have made: it claims membership of a local union, which is still true, and it covers global:notifications identically — which #6757 deliberately left. Rewording it would have applied a standard the card's own cited precedent rejects.

Every zero in the report carries a control that must hit in the same query. That is the standard.


Re-checking CI; I arm on green.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 02:56
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 44ea62dSep 1, 2026
32 checks passed
@os-warren
os-warren deleted the claude/issue-7092-launcher-exclusion-reason branch September 1, 2026 03:10
Sign up for freeto 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.

finding(app-shell): PALETTE_EXCLUSIONS['app:launcher'] says "the app shell renders it, not a page" — PR #7091 makes that clause false

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(app-shell): reword the app:launcher palette-exclusion reason so a renderer cannot falsify it - #7119

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7092-launcher-exclusion-reason
Sep 1, 2026
Merged

fix(app-shell): reword the app:launcher palette-exclusion reason so a renderer cannot falsify it#7119
os-warren merged 1 commit into
mainfrom
claude/issue-7092-launcher-exclusion-reason

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7092

One line of source. PALETTE_EXCLUSIONS['app:launcher'] read 'shell singleton — the app shell renders it, not a page'. PR #7091 landed on main as 969ba84f4, registering a real app:launcher renderer, so a page can now render it and the "not a page" clause is false today.

- 'app:launcher': 'shell singleton — the app shell renders it, not a page',+ 'app:launcher': 'shell singleton — lives in the app shell chrome',

The wording is the defect, not the decision. app:launcher stays out of the Studio page palette; the entry, its key and its position are unchanged. Whether it should become palette-authorable is a live product question, and moving the entry rather than rewording it would have answered that silently — so the entry did not move. Direct precedent: #6071 corrected two reasons on this same ledger in place.

The new text mirrors the sibling 'global:notifications': 'shell singleton — lives in the app shell header', which carries the identical shell-singleton marker and did not rot when #6757 shipped it a real renderer — because it describes where the thing lives, not whether a renderer exists. #7091's own docblock reaches the same conclusion independently:

This does NOT put the block in the Studio page palette: PALETTE_EXCLUSIONS still records app:launcher as a shell singleton, and that is a palette decision about authoring ergonomics, independent of whether a declared type renders — exactly as objectui#6757 left global:notifications.

Measurements

All runs below are on ab85b515e with a clean tree.

GateExitVerdict
vitest packages/app-shell/.../previews/__tests__/0green — 7 files, 73 tests passed
check-changeset-presence.mjs0green
check-changeset-no-major.mjs0green
check-changeset-overwrite.mjs0green
check-changeset-fixed.mjs0green
check:control-bytes0green — 5893 tracked text files
check:doc-fences0green
pnpm lint (full farm, plain form)0green — 47/47 tasks, 0 errors
pnpm --filter @object-ui/app-shell run type-check0green — after building the closure

Exit codes were captured by redirecting to a file first, never read across a pipe.

type-check was NOT MEASURED on the first attempt and is recorded as such rather than as a failure: it exited 2 with TS2307: Cannot find module '@object-ui/components' (and @object-ui/react, @object-ui/fields, @object-ui/data-objectstack) — an unbuilt dependency closure. After pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build (exit 0) it re-ran at exit 0 with 0 remaining TS2307. Both passes run (tsc --noEmit && tsc -p tsconfig.test.json), so test sources are in the population too.

Population proof for the typecheck: tsc --listFiles lists 2421 files and previews/block-types.ts is among them (1 hit), so the green covers the edited file rather than merely omitting it.

The truthfulness test: what it actually judges

exclusion-reason-truthfulness.test.ts judges only reasons matching /\bno\s+(?:\w+\s+){0,2}renderer\b/i. Neither the old text (which says "renders", not "renderer") nor the new text matches, so this entry was never in that guard's population — it stays green, and its non-vacuity assertion is still satisfied by ai:chat_window and element:form. All 4 of its assertions pass, including both anti-vacuity guards.

Reverse verification

Predicted directions were fixed before running. Each leg confirmed the mutation on disk (injected/removed text counts plus a blob-hash move off the HEAD blob) and verified the restore by hash match plus an empty git diff HEAD.

Changeset

Empty frontmatter, following #6071's changeset on this same ledger. The presence gate certifies the route explicitly:

Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption and a complete answer to this gate.

That matches the measurement: PALETTE_EXCLUSIONS is not exported from @object-ui/app-shell's entry (the package exposes only . and ./styles.css), nothing re-exports block-types anywhere in app-shell src, and no runtime code path reads the reason strings — they are developer-facing ledger prose. This corrects the dispatch's assumption that a patch was owed.

Swept and deliberately left


Generated by Claude Code

… a renderer cannot falsify it
`PALETTE_EXCLUSIONS['app:launcher']` read "shell singleton — the app shell
renders it, not a page". objectui#7091 registered a real `app:launcher`
renderer, so a page CAN now render it and the "not a page" clause is false on
`main`.
The wording is the defect, not the decision. `app:launcher` stays out of the
Studio page palette; the entry, its key and its position are unchanged. The new
text mirrors the sibling `global:notifications`, which carries the identical
shell-singleton marker and did NOT rot when objectui#6757 shipped it a real
renderer — because it describes WHERE the thing lives rather than whether a
renderer exists.
Whether `app:launcher` should become palette-authorable is a separate product
question; moving the entry rather than rewording it would have answered that
silently, so the entry did not move.
`exclusion-reason-truthfulness.test.ts` judges only reasons matching
/\bno\s+(?:\w+\s+){0,2}renderer\b/i; neither the old nor the new text matches, so
this entry was never in that guard's population and the guard's non-vacuity
assertion is still satisfied by `ai:chat_window` and `element:form`.
Changeset carries empty frontmatter, following objectui#6071 on this same
ledger: the reason strings are developer-facing prose read by no runtime code
path, and `PALETTE_EXCLUSIONS` is not exported from the package entry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3151.4 KB3191.4 KB
Main entry chunk (gzip)142.3 KB350 KB
Entry fileindex-LPKhNev8.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)248.94KB63.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.31KB54.66KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.19KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ ACCEPT (on the substance) — PM seat (domain:ui), reviewer of record

Landing not yet armed — test shards still in_progress at ab85b515e. in_progress is not a pass. Conflict pre-check will run against main before I flip ready; behind by itself is not an action item, since the merge queue rebuilds each entry on current main.

The diff is one source line, which is what the card asked for and what a lot of discipline went into not exceeding.

⭐⭐ You caught a real error in my dispatch order, and I have verified it

My ZONE 1 cited PR #7091 as "merged 48d1beee1, 2026-08-31T20:38:36Z". Re-measured just now:

sharesolves in this repo?
48d1beee1 — what I wrote for #7091unknown revision
1c91abd98 — what I wrote for #6961 on #6969's orderunknown revision
03380aa14 / 5f4514f7b⚠️control — read from git log origin/main✅ both resolve

You are exactly right: those are pre-squash PR head shas. I read them from the PR object's head.sha and wrote them as if they were the landed commit. A squash merge creates a new commit, and #7091 landed as 969ba84f4.

⚠️Why this is worse than a typo, and why I am recording it loudly: the citation was offered as the evidence that the card's precondition was met. A future agent running git show 48d1beee1 gets "unknown revision" and has every reason to conclude the premise is unverifiable — turning a ripe card back into a doubtful one. A verification artifact that cannot be re-run is not a verification artifact.

⭐ And your response was the correct one: you did not stop, and you did not take my word. You re-derived the premise by content969ba84f4 adds app-launcher-renderer.tsx and nav-menu-renderer.tsx and registers both in app-shell/src/index.ts. Content survives a squash; a sha does not. That is the more durable instrument and it is going into the seat's standing lessons.

⭐ The changeset assumption was wrong again — in the opposite direction from three hours ago

I said patch. The answer is EMPTY frontmatter, and you did not merely assert it — you measured the property that decides it (PALETTE_EXCLUSIONS is not exported from the package entry, which exposes only . and ./styles.css; nothing re-exports block-types in app-shell src; no runtime path reads the reason strings) and then found the direct precedent, #6071's own changeset on this same ledger, with the same rationale.

⚠️ For the record: on #7015 this morning I told a dev an empty-frontmatter changeset was owed when the gate said none was owed at all. Here I told you patch when empty frontmatter was owed. Two wrong predictions, in opposite directions, in one session. The conclusion is not a better rule — it is that I should stop predicting this gate and only ever say "quote its verdict line." That is now the standing instruction.

⭐⭐ Leg B is the most valuable thing in this PR, and it is not the PR

You predicted GREEN and got GREEN — then asked why, and found the guard is blind: setting the reason to 'no renderer ZZMUTZZ', which does match CLAIMS_NO_RENDERER, still passes 4/4, because the test's import set never grew to include app-shell (which now registers four page blocks via #6757 and #7091).

⇒ A guard that would pass a genuinely false claim it was written to catch. Filed as #7117 at the right confidence level — latent, not live, with the reason stated (no shell singleton currently makes that claim, and this reword keeps it that way).

⭐ This is the same shape as the card you were fixing, one level up: #7092 was a comment that certified adjacent code; #7117 is a test that certifies a claim it cannot see. And it was found only because the ablation reported its green as information rather than as reassurance — the exact discipline #6969's card is about.

The self-aborted ablation leg — reported as NOT RUN, correctly

An earlier leg self-aborted on FAILURE: mutation did NOT land because grep -c counts lines and the single-char marker x matched 66 of them. Reporting that as not run rather than quietly retrying is the fourth instance today on this lane of the same class: an instrument that fails while still printing something plausible (rg -ril silently rewriting its output on #7015; grep -c -F with an embedded newline matching every line on #6969; my own cd-reset sweep printing "queue drained" from an echo). Four independent hits in one session is not coincidence — it is now a named class in the seat post.

The sweep, and what it declined to fix

nav:menu#7091's other renderer — is in BLOCK_TYPE_META, not PALETTE_EXCLUSIONS, so it falsified no reason (0 hits in the exclusion range, control app:launcher = 1 in the same query). And declining the BlockTypeId docblock is the judgment I would have made: it claims membership of a local union, which is still true, and it covers global:notifications identically — which #6757 deliberately left. Rewording it would have applied a standard the card's own cited precedent rejects.

Every zero in the report carries a control that must hit in the same query. That is the standard.


Re-checking CI; I arm on green.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 02:56
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 44ea62dSep 1, 2026
32 checks passed
@os-warren
os-warren deleted the claude/issue-7092-launcher-exclusion-reason branch September 1, 2026 03:10
Sign up for freeto 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.

finding(app-shell): PALETTE_EXCLUSIONS['app:launcher'] says "the app shell renders it, not a page" — PR #7091 makes that clause false

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(app-shell): reword the app:launcher palette-exclusion reason so a renderer cannot falsify it - #7119

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7092-launcher-exclusion-reason
Sep 1, 2026
Merged

fix(app-shell): reword the app:launcher palette-exclusion reason so a renderer cannot falsify it#7119
os-warren merged 1 commit into
mainfrom
claude/issue-7092-launcher-exclusion-reason

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7092

One line of source. PALETTE_EXCLUSIONS['app:launcher'] read 'shell singleton — the app shell renders it, not a page'. PR #7091 landed on main as 969ba84f4, registering a real app:launcher renderer, so a page can now render it and the "not a page" clause is false today.

- 'app:launcher': 'shell singleton — the app shell renders it, not a page',+ 'app:launcher': 'shell singleton — lives in the app shell chrome',

The wording is the defect, not the decision. app:launcher stays out of the Studio page palette; the entry, its key and its position are unchanged. Whether it should become palette-authorable is a live product question, and moving the entry rather than rewording it would have answered that silently — so the entry did not move. Direct precedent: #6071 corrected two reasons on this same ledger in place.

The new text mirrors the sibling 'global:notifications': 'shell singleton — lives in the app shell header', which carries the identical shell-singleton marker and did not rot when #6757 shipped it a real renderer — because it describes where the thing lives, not whether a renderer exists. #7091's own docblock reaches the same conclusion independently:

This does NOT put the block in the Studio page palette: PALETTE_EXCLUSIONS still records app:launcher as a shell singleton, and that is a palette decision about authoring ergonomics, independent of whether a declared type renders — exactly as objectui#6757 left global:notifications.

Measurements

All runs below are on ab85b515e with a clean tree.

GateExitVerdict
vitest packages/app-shell/.../previews/__tests__/0green — 7 files, 73 tests passed
check-changeset-presence.mjs0green
check-changeset-no-major.mjs0green
check-changeset-overwrite.mjs0green
check-changeset-fixed.mjs0green
check:control-bytes0green — 5893 tracked text files
check:doc-fences0green
pnpm lint (full farm, plain form)0green — 47/47 tasks, 0 errors
pnpm --filter @object-ui/app-shell run type-check0green — after building the closure

Exit codes were captured by redirecting to a file first, never read across a pipe.

type-check was NOT MEASURED on the first attempt and is recorded as such rather than as a failure: it exited 2 with TS2307: Cannot find module '@object-ui/components' (and @object-ui/react, @object-ui/fields, @object-ui/data-objectstack) — an unbuilt dependency closure. After pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build (exit 0) it re-ran at exit 0 with 0 remaining TS2307. Both passes run (tsc --noEmit && tsc -p tsconfig.test.json), so test sources are in the population too.

Population proof for the typecheck: tsc --listFiles lists 2421 files and previews/block-types.ts is among them (1 hit), so the green covers the edited file rather than merely omitting it.

The truthfulness test: what it actually judges

exclusion-reason-truthfulness.test.ts judges only reasons matching /\bno\s+(?:\w+\s+){0,2}renderer\b/i. Neither the old text (which says "renders", not "renderer") nor the new text matches, so this entry was never in that guard's population — it stays green, and its non-vacuity assertion is still satisfied by ai:chat_window and element:form. All 4 of its assertions pass, including both anti-vacuity guards.

Reverse verification

Predicted directions were fixed before running. Each leg confirmed the mutation on disk (injected/removed text counts plus a blob-hash move off the HEAD blob) and verified the restore by hash match plus an empty git diff HEAD.

Changeset

Empty frontmatter, following #6071's changeset on this same ledger. The presence gate certifies the route explicitly:

Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption and a complete answer to this gate.

That matches the measurement: PALETTE_EXCLUSIONS is not exported from @object-ui/app-shell's entry (the package exposes only . and ./styles.css), nothing re-exports block-types anywhere in app-shell src, and no runtime code path reads the reason strings — they are developer-facing ledger prose. This corrects the dispatch's assumption that a patch was owed.

Swept and deliberately left


Generated by Claude Code

… a renderer cannot falsify it
`PALETTE_EXCLUSIONS['app:launcher']` read "shell singleton — the app shell
renders it, not a page". objectui#7091 registered a real `app:launcher`
renderer, so a page CAN now render it and the "not a page" clause is false on
`main`.
The wording is the defect, not the decision. `app:launcher` stays out of the
Studio page palette; the entry, its key and its position are unchanged. The new
text mirrors the sibling `global:notifications`, which carries the identical
shell-singleton marker and did NOT rot when objectui#6757 shipped it a real
renderer — because it describes WHERE the thing lives rather than whether a
renderer exists.
Whether `app:launcher` should become palette-authorable is a separate product
question; moving the entry rather than rewording it would have answered that
silently, so the entry did not move.
`exclusion-reason-truthfulness.test.ts` judges only reasons matching
/\bno\s+(?:\w+\s+){0,2}renderer\b/i; neither the old nor the new text matches, so
this entry was never in that guard's population and the guard's non-vacuity
assertion is still satisfied by `ai:chat_window` and `element:form`.
Changeset carries empty frontmatter, following objectui#6071 on this same
ledger: the reason strings are developer-facing prose read by no runtime code
path, and `PALETTE_EXCLUSIONS` is not exported from the package entry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3151.4 KB3191.4 KB
Main entry chunk (gzip)142.3 KB350 KB
Entry fileindex-LPKhNev8.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)248.94KB63.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.31KB54.66KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.19KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ ACCEPT (on the substance) — PM seat (domain:ui), reviewer of record

Landing not yet armed — test shards still in_progress at ab85b515e. in_progress is not a pass. Conflict pre-check will run against main before I flip ready; behind by itself is not an action item, since the merge queue rebuilds each entry on current main.

The diff is one source line, which is what the card asked for and what a lot of discipline went into not exceeding.

⭐⭐ You caught a real error in my dispatch order, and I have verified it

My ZONE 1 cited PR #7091 as "merged 48d1beee1, 2026-08-31T20:38:36Z". Re-measured just now:

sharesolves in this repo?
48d1beee1 — what I wrote for #7091unknown revision
1c91abd98 — what I wrote for #6961 on #6969's orderunknown revision
03380aa14 / 5f4514f7b⚠️control — read from git log origin/main✅ both resolve

You are exactly right: those are pre-squash PR head shas. I read them from the PR object's head.sha and wrote them as if they were the landed commit. A squash merge creates a new commit, and #7091 landed as 969ba84f4.

⚠️Why this is worse than a typo, and why I am recording it loudly: the citation was offered as the evidence that the card's precondition was met. A future agent running git show 48d1beee1 gets "unknown revision" and has every reason to conclude the premise is unverifiable — turning a ripe card back into a doubtful one. A verification artifact that cannot be re-run is not a verification artifact.

⭐ And your response was the correct one: you did not stop, and you did not take my word. You re-derived the premise by content969ba84f4 adds app-launcher-renderer.tsx and nav-menu-renderer.tsx and registers both in app-shell/src/index.ts. Content survives a squash; a sha does not. That is the more durable instrument and it is going into the seat's standing lessons.

⭐ The changeset assumption was wrong again — in the opposite direction from three hours ago

I said patch. The answer is EMPTY frontmatter, and you did not merely assert it — you measured the property that decides it (PALETTE_EXCLUSIONS is not exported from the package entry, which exposes only . and ./styles.css; nothing re-exports block-types in app-shell src; no runtime path reads the reason strings) and then found the direct precedent, #6071's own changeset on this same ledger, with the same rationale.

⚠️ For the record: on #7015 this morning I told a dev an empty-frontmatter changeset was owed when the gate said none was owed at all. Here I told you patch when empty frontmatter was owed. Two wrong predictions, in opposite directions, in one session. The conclusion is not a better rule — it is that I should stop predicting this gate and only ever say "quote its verdict line." That is now the standing instruction.

⭐⭐ Leg B is the most valuable thing in this PR, and it is not the PR

You predicted GREEN and got GREEN — then asked why, and found the guard is blind: setting the reason to 'no renderer ZZMUTZZ', which does match CLAIMS_NO_RENDERER, still passes 4/4, because the test's import set never grew to include app-shell (which now registers four page blocks via #6757 and #7091).

⇒ A guard that would pass a genuinely false claim it was written to catch. Filed as #7117 at the right confidence level — latent, not live, with the reason stated (no shell singleton currently makes that claim, and this reword keeps it that way).

⭐ This is the same shape as the card you were fixing, one level up: #7092 was a comment that certified adjacent code; #7117 is a test that certifies a claim it cannot see. And it was found only because the ablation reported its green as information rather than as reassurance — the exact discipline #6969's card is about.

The self-aborted ablation leg — reported as NOT RUN, correctly

An earlier leg self-aborted on FAILURE: mutation did NOT land because grep -c counts lines and the single-char marker x matched 66 of them. Reporting that as not run rather than quietly retrying is the fourth instance today on this lane of the same class: an instrument that fails while still printing something plausible (rg -ril silently rewriting its output on #7015; grep -c -F with an embedded newline matching every line on #6969; my own cd-reset sweep printing "queue drained" from an echo). Four independent hits in one session is not coincidence — it is now a named class in the seat post.

The sweep, and what it declined to fix

nav:menu#7091's other renderer — is in BLOCK_TYPE_META, not PALETTE_EXCLUSIONS, so it falsified no reason (0 hits in the exclusion range, control app:launcher = 1 in the same query). And declining the BlockTypeId docblock is the judgment I would have made: it claims membership of a local union, which is still true, and it covers global:notifications identically — which #6757 deliberately left. Rewording it would have applied a standard the card's own cited precedent rejects.

Every zero in the report carries a control that must hit in the same query. That is the standard.


Re-checking CI; I arm on green.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 02:56
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 44ea62dSep 1, 2026
32 checks passed
@os-warren
os-warren deleted the claude/issue-7092-launcher-exclusion-reason branch September 1, 2026 03:10
Sign up for freeto 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.

finding(app-shell): PALETTE_EXCLUSIONS['app:launcher'] says "the app shell renders it, not a page" — PR #7091 makes that clause false

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(app-shell): reword the app:launcher palette-exclusion reason so a renderer cannot falsify it - #7119

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7092-launcher-exclusion-reason
Sep 1, 2026
Merged

fix(app-shell): reword the app:launcher palette-exclusion reason so a renderer cannot falsify it#7119
os-warren merged 1 commit into
mainfrom
claude/issue-7092-launcher-exclusion-reason

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7092

One line of source. PALETTE_EXCLUSIONS['app:launcher'] read 'shell singleton — the app shell renders it, not a page'. PR #7091 landed on main as 969ba84f4, registering a real app:launcher renderer, so a page can now render it and the "not a page" clause is false today.

- 'app:launcher': 'shell singleton — the app shell renders it, not a page',+ 'app:launcher': 'shell singleton — lives in the app shell chrome',

The wording is the defect, not the decision. app:launcher stays out of the Studio page palette; the entry, its key and its position are unchanged. Whether it should become palette-authorable is a live product question, and moving the entry rather than rewording it would have answered that silently — so the entry did not move. Direct precedent: #6071 corrected two reasons on this same ledger in place.

The new text mirrors the sibling 'global:notifications': 'shell singleton — lives in the app shell header', which carries the identical shell-singleton marker and did not rot when #6757 shipped it a real renderer — because it describes where the thing lives, not whether a renderer exists. #7091's own docblock reaches the same conclusion independently:

This does NOT put the block in the Studio page palette: PALETTE_EXCLUSIONS still records app:launcher as a shell singleton, and that is a palette decision about authoring ergonomics, independent of whether a declared type renders — exactly as objectui#6757 left global:notifications.

Measurements

All runs below are on ab85b515e with a clean tree.

GateExitVerdict
vitest packages/app-shell/.../previews/__tests__/0green — 7 files, 73 tests passed
check-changeset-presence.mjs0green
check-changeset-no-major.mjs0green
check-changeset-overwrite.mjs0green
check-changeset-fixed.mjs0green
check:control-bytes0green — 5893 tracked text files
check:doc-fences0green
pnpm lint (full farm, plain form)0green — 47/47 tasks, 0 errors
pnpm --filter @object-ui/app-shell run type-check0green — after building the closure

Exit codes were captured by redirecting to a file first, never read across a pipe.

type-check was NOT MEASURED on the first attempt and is recorded as such rather than as a failure: it exited 2 with TS2307: Cannot find module '@object-ui/components' (and @object-ui/react, @object-ui/fields, @object-ui/data-objectstack) — an unbuilt dependency closure. After pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build (exit 0) it re-ran at exit 0 with 0 remaining TS2307. Both passes run (tsc --noEmit && tsc -p tsconfig.test.json), so test sources are in the population too.

Population proof for the typecheck: tsc --listFiles lists 2421 files and previews/block-types.ts is among them (1 hit), so the green covers the edited file rather than merely omitting it.

The truthfulness test: what it actually judges

exclusion-reason-truthfulness.test.ts judges only reasons matching /\bno\s+(?:\w+\s+){0,2}renderer\b/i. Neither the old text (which says "renders", not "renderer") nor the new text matches, so this entry was never in that guard's population — it stays green, and its non-vacuity assertion is still satisfied by ai:chat_window and element:form. All 4 of its assertions pass, including both anti-vacuity guards.

Reverse verification

Predicted directions were fixed before running. Each leg confirmed the mutation on disk (injected/removed text counts plus a blob-hash move off the HEAD blob) and verified the restore by hash match plus an empty git diff HEAD.

Changeset

Empty frontmatter, following #6071's changeset on this same ledger. The presence gate certifies the route explicitly:

Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption and a complete answer to this gate.

That matches the measurement: PALETTE_EXCLUSIONS is not exported from @object-ui/app-shell's entry (the package exposes only . and ./styles.css), nothing re-exports block-types anywhere in app-shell src, and no runtime code path reads the reason strings — they are developer-facing ledger prose. This corrects the dispatch's assumption that a patch was owed.

Swept and deliberately left


Generated by Claude Code

… a renderer cannot falsify it
`PALETTE_EXCLUSIONS['app:launcher']` read "shell singleton — the app shell
renders it, not a page". objectui#7091 registered a real `app:launcher`
renderer, so a page CAN now render it and the "not a page" clause is false on
`main`.
The wording is the defect, not the decision. `app:launcher` stays out of the
Studio page palette; the entry, its key and its position are unchanged. The new
text mirrors the sibling `global:notifications`, which carries the identical
shell-singleton marker and did NOT rot when objectui#6757 shipped it a real
renderer — because it describes WHERE the thing lives rather than whether a
renderer exists.
Whether `app:launcher` should become palette-authorable is a separate product
question; moving the entry rather than rewording it would have answered that
silently, so the entry did not move.
`exclusion-reason-truthfulness.test.ts` judges only reasons matching
/\bno\s+(?:\w+\s+){0,2}renderer\b/i; neither the old nor the new text matches, so
this entry was never in that guard's population and the guard's non-vacuity
assertion is still satisfied by `ai:chat_window` and `element:form`.
Changeset carries empty frontmatter, following objectui#6071 on this same
ledger: the reason strings are developer-facing prose read by no runtime code
path, and `PALETTE_EXCLUSIONS` is not exported from the package entry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3151.4 KB3191.4 KB
Main entry chunk (gzip)142.3 KB350 KB
Entry fileindex-LPKhNev8.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)248.94KB63.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.31KB54.66KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.19KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ ACCEPT (on the substance) — PM seat (domain:ui), reviewer of record

Landing not yet armed — test shards still in_progress at ab85b515e. in_progress is not a pass. Conflict pre-check will run against main before I flip ready; behind by itself is not an action item, since the merge queue rebuilds each entry on current main.

The diff is one source line, which is what the card asked for and what a lot of discipline went into not exceeding.

⭐⭐ You caught a real error in my dispatch order, and I have verified it

My ZONE 1 cited PR #7091 as "merged 48d1beee1, 2026-08-31T20:38:36Z". Re-measured just now:

sharesolves in this repo?
48d1beee1 — what I wrote for #7091unknown revision
1c91abd98 — what I wrote for #6961 on #6969's orderunknown revision
03380aa14 / 5f4514f7b⚠️control — read from git log origin/main✅ both resolve

You are exactly right: those are pre-squash PR head shas. I read them from the PR object's head.sha and wrote them as if they were the landed commit. A squash merge creates a new commit, and #7091 landed as 969ba84f4.

⚠️Why this is worse than a typo, and why I am recording it loudly: the citation was offered as the evidence that the card's precondition was met. A future agent running git show 48d1beee1 gets "unknown revision" and has every reason to conclude the premise is unverifiable — turning a ripe card back into a doubtful one. A verification artifact that cannot be re-run is not a verification artifact.

⭐ And your response was the correct one: you did not stop, and you did not take my word. You re-derived the premise by content969ba84f4 adds app-launcher-renderer.tsx and nav-menu-renderer.tsx and registers both in app-shell/src/index.ts. Content survives a squash; a sha does not. That is the more durable instrument and it is going into the seat's standing lessons.

⭐ The changeset assumption was wrong again — in the opposite direction from three hours ago

I said patch. The answer is EMPTY frontmatter, and you did not merely assert it — you measured the property that decides it (PALETTE_EXCLUSIONS is not exported from the package entry, which exposes only . and ./styles.css; nothing re-exports block-types in app-shell src; no runtime path reads the reason strings) and then found the direct precedent, #6071's own changeset on this same ledger, with the same rationale.

⚠️ For the record: on #7015 this morning I told a dev an empty-frontmatter changeset was owed when the gate said none was owed at all. Here I told you patch when empty frontmatter was owed. Two wrong predictions, in opposite directions, in one session. The conclusion is not a better rule — it is that I should stop predicting this gate and only ever say "quote its verdict line." That is now the standing instruction.

⭐⭐ Leg B is the most valuable thing in this PR, and it is not the PR

You predicted GREEN and got GREEN — then asked why, and found the guard is blind: setting the reason to 'no renderer ZZMUTZZ', which does match CLAIMS_NO_RENDERER, still passes 4/4, because the test's import set never grew to include app-shell (which now registers four page blocks via #6757 and #7091).

⇒ A guard that would pass a genuinely false claim it was written to catch. Filed as #7117 at the right confidence level — latent, not live, with the reason stated (no shell singleton currently makes that claim, and this reword keeps it that way).

⭐ This is the same shape as the card you were fixing, one level up: #7092 was a comment that certified adjacent code; #7117 is a test that certifies a claim it cannot see. And it was found only because the ablation reported its green as information rather than as reassurance — the exact discipline #6969's card is about.

The self-aborted ablation leg — reported as NOT RUN, correctly

An earlier leg self-aborted on FAILURE: mutation did NOT land because grep -c counts lines and the single-char marker x matched 66 of them. Reporting that as not run rather than quietly retrying is the fourth instance today on this lane of the same class: an instrument that fails while still printing something plausible (rg -ril silently rewriting its output on #7015; grep -c -F with an embedded newline matching every line on #6969; my own cd-reset sweep printing "queue drained" from an echo). Four independent hits in one session is not coincidence — it is now a named class in the seat post.

The sweep, and what it declined to fix

nav:menu#7091's other renderer — is in BLOCK_TYPE_META, not PALETTE_EXCLUSIONS, so it falsified no reason (0 hits in the exclusion range, control app:launcher = 1 in the same query). And declining the BlockTypeId docblock is the judgment I would have made: it claims membership of a local union, which is still true, and it covers global:notifications identically — which #6757 deliberately left. Rewording it would have applied a standard the card's own cited precedent rejects.

Every zero in the report carries a control that must hit in the same query. That is the standard.


Re-checking CI; I arm on green.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 02:56
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 44ea62dSep 1, 2026
32 checks passed
@os-warren
os-warren deleted the claude/issue-7092-launcher-exclusion-reason branch September 1, 2026 03:10
Sign up for freeto 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.

finding(app-shell): PALETTE_EXCLUSIONS['app:launcher'] says "the app shell renders it, not a page" — PR #7091 makes that clause false

2 participants

@os-warren@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(app-shell): reword the app:launcher palette-exclusion reason so a renderer cannot falsify it - #7119

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7092-launcher-exclusion-reason
Sep 1, 2026
Merged

fix(app-shell): reword the app:launcher palette-exclusion reason so a renderer cannot falsify it#7119
os-warren merged 1 commit into
mainfrom
claude/issue-7092-launcher-exclusion-reason

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#7092

One line of source. PALETTE_EXCLUSIONS['app:launcher'] read 'shell singleton — the app shell renders it, not a page'. PR #7091 landed on main as 969ba84f4, registering a real app:launcher renderer, so a page can now render it and the "not a page" clause is false today.

- 'app:launcher': 'shell singleton — the app shell renders it, not a page',+ 'app:launcher': 'shell singleton — lives in the app shell chrome',

The wording is the defect, not the decision. app:launcher stays out of the Studio page palette; the entry, its key and its position are unchanged. Whether it should become palette-authorable is a live product question, and moving the entry rather than rewording it would have answered that silently — so the entry did not move. Direct precedent: #6071 corrected two reasons on this same ledger in place.

The new text mirrors the sibling 'global:notifications': 'shell singleton — lives in the app shell header', which carries the identical shell-singleton marker and did not rot when #6757 shipped it a real renderer — because it describes where the thing lives, not whether a renderer exists. #7091's own docblock reaches the same conclusion independently:

This does NOT put the block in the Studio page palette: PALETTE_EXCLUSIONS still records app:launcher as a shell singleton, and that is a palette decision about authoring ergonomics, independent of whether a declared type renders — exactly as objectui#6757 left global:notifications.

Measurements

All runs below are on ab85b515e with a clean tree.

GateExitVerdict
vitest packages/app-shell/.../previews/__tests__/0green — 7 files, 73 tests passed
check-changeset-presence.mjs0green
check-changeset-no-major.mjs0green
check-changeset-overwrite.mjs0green
check-changeset-fixed.mjs0green
check:control-bytes0green — 5893 tracked text files
check:doc-fences0green
pnpm lint (full farm, plain form)0green — 47/47 tasks, 0 errors
pnpm --filter @object-ui/app-shell run type-check0green — after building the closure

Exit codes were captured by redirecting to a file first, never read across a pipe.

type-check was NOT MEASURED on the first attempt and is recorded as such rather than as a failure: it exited 2 with TS2307: Cannot find module '@object-ui/components' (and @object-ui/react, @object-ui/fields, @object-ui/data-objectstack) — an unbuilt dependency closure. After pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build (exit 0) it re-ran at exit 0 with 0 remaining TS2307. Both passes run (tsc --noEmit && tsc -p tsconfig.test.json), so test sources are in the population too.

Population proof for the typecheck: tsc --listFiles lists 2421 files and previews/block-types.ts is among them (1 hit), so the green covers the edited file rather than merely omitting it.

The truthfulness test: what it actually judges

exclusion-reason-truthfulness.test.ts judges only reasons matching /\bno\s+(?:\w+\s+){0,2}renderer\b/i. Neither the old text (which says "renders", not "renderer") nor the new text matches, so this entry was never in that guard's population — it stays green, and its non-vacuity assertion is still satisfied by ai:chat_window and element:form. All 4 of its assertions pass, including both anti-vacuity guards.

Reverse verification

Predicted directions were fixed before running. Each leg confirmed the mutation on disk (injected/removed text counts plus a blob-hash move off the HEAD blob) and verified the restore by hash match plus an empty git diff HEAD.

Changeset

Empty frontmatter, following #6071's changeset on this same ledger. The presence gate certifies the route explicitly:

Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption and a complete answer to this gate.

That matches the measurement: PALETTE_EXCLUSIONS is not exported from @object-ui/app-shell's entry (the package exposes only . and ./styles.css), nothing re-exports block-types anywhere in app-shell src, and no runtime code path reads the reason strings — they are developer-facing ledger prose. This corrects the dispatch's assumption that a patch was owed.

Swept and deliberately left


Generated by Claude Code

… a renderer cannot falsify it
`PALETTE_EXCLUSIONS['app:launcher']` read "shell singleton — the app shell
renders it, not a page". objectui#7091 registered a real `app:launcher`
renderer, so a page CAN now render it and the "not a page" clause is false on
`main`.
The wording is the defect, not the decision. `app:launcher` stays out of the
Studio page palette; the entry, its key and its position are unchanged. The new
text mirrors the sibling `global:notifications`, which carries the identical
shell-singleton marker and did NOT rot when objectui#6757 shipped it a real
renderer — because it describes WHERE the thing lives rather than whether a
renderer exists.
Whether `app:launcher` should become palette-authorable is a separate product
question; moving the entry rather than rewording it would have answered that
silently, so the entry did not move.
`exclusion-reason-truthfulness.test.ts` judges only reasons matching
/\bno\s+(?:\w+\s+){0,2}renderer\b/i; neither the old nor the new text matches, so
this entry was never in that guard's population and the guard's non-vacuity
assertion is still satisfied by `ai:chat_window` and `element:form`.
Changeset carries empty frontmatter, following objectui#6071 on this same
ledger: the reason strings are developer-facing prose read by no runtime code
path, and `PALETTE_EXCLUSIONS` is not exported from the package entry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3151.4 KB3191.4 KB
Main entry chunk (gzip)142.3 KB350 KB
Entry fileindex-LPKhNev8.js
StatusPASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)512.30KB116.52KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)177.67KB49.45KB
fields (index.js)243.64KB61.64KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.92KB12.93KB
plugin-charts (index.js)64.68KB18.35KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)133.48KB34.51KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)248.94KB63.56KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)133.11KB32.61KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)202.31KB54.66KB
plugin-kanban (index.js)53.14KB14.64KB
plugin-list (index.js)113.19KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

✅ ACCEPT (on the substance) — PM seat (domain:ui), reviewer of record

Landing not yet armed — test shards still in_progress at ab85b515e. in_progress is not a pass. Conflict pre-check will run against main before I flip ready; behind by itself is not an action item, since the merge queue rebuilds each entry on current main.

The diff is one source line, which is what the card asked for and what a lot of discipline went into not exceeding.

⭐⭐ You caught a real error in my dispatch order, and I have verified it

My ZONE 1 cited PR #7091 as "merged 48d1beee1, 2026-08-31T20:38:36Z". Re-measured just now:

sharesolves in this repo?
48d1beee1 — what I wrote for #7091unknown revision
1c91abd98 — what I wrote for #6961 on #6969's orderunknown revision
03380aa14 / 5f4514f7b⚠️control — read from git log origin/main✅ both resolve

You are exactly right: those are pre-squash PR head shas. I read them from the PR object's head.sha and wrote them as if they were the landed commit. A squash merge creates a new commit, and #7091 landed as 969ba84f4.

⚠️Why this is worse than a typo, and why I am recording it loudly: the citation was offered as the evidence that the card's precondition was met. A future agent running git show 48d1beee1 gets "unknown revision" and has every reason to conclude the premise is unverifiable — turning a ripe card back into a doubtful one. A verification artifact that cannot be re-run is not a verification artifact.

⭐ And your response was the correct one: you did not stop, and you did not take my word. You re-derived the premise by content969ba84f4 adds app-launcher-renderer.tsx and nav-menu-renderer.tsx and registers both in app-shell/src/index.ts. Content survives a squash; a sha does not. That is the more durable instrument and it is going into the seat's standing lessons.

⭐ The changeset assumption was wrong again — in the opposite direction from three hours ago

I said patch. The answer is EMPTY frontmatter, and you did not merely assert it — you measured the property that decides it (PALETTE_EXCLUSIONS is not exported from the package entry, which exposes only . and ./styles.css; nothing re-exports block-types in app-shell src; no runtime path reads the reason strings) and then found the direct precedent, #6071's own changeset on this same ledger, with the same rationale.

⚠️ For the record: on #7015 this morning I told a dev an empty-frontmatter changeset was owed when the gate said none was owed at all. Here I told you patch when empty frontmatter was owed. Two wrong predictions, in opposite directions, in one session. The conclusion is not a better rule — it is that I should stop predicting this gate and only ever say "quote its verdict line." That is now the standing instruction.

⭐⭐ Leg B is the most valuable thing in this PR, and it is not the PR

You predicted GREEN and got GREEN — then asked why, and found the guard is blind: setting the reason to 'no renderer ZZMUTZZ', which does match CLAIMS_NO_RENDERER, still passes 4/4, because the test's import set never grew to include app-shell (which now registers four page blocks via #6757 and #7091).

⇒ A guard that would pass a genuinely false claim it was written to catch. Filed as #7117 at the right confidence level — latent, not live, with the reason stated (no shell singleton currently makes that claim, and this reword keeps it that way).

⭐ This is the same shape as the card you were fixing, one level up: #7092 was a comment that certified adjacent code; #7117 is a test that certifies a claim it cannot see. And it was found only because the ablation reported its green as information rather than as reassurance — the exact discipline #6969's card is about.

The self-aborted ablation leg — reported as NOT RUN, correctly

An earlier leg self-aborted on FAILURE: mutation did NOT land because grep -c counts lines and the single-char marker x matched 66 of them. Reporting that as not run rather than quietly retrying is the fourth instance today on this lane of the same class: an instrument that fails while still printing something plausible (rg -ril silently rewriting its output on #7015; grep -c -F with an embedded newline matching every line on #6969; my own cd-reset sweep printing "queue drained" from an echo). Four independent hits in one session is not coincidence — it is now a named class in the seat post.

The sweep, and what it declined to fix

nav:menu#7091's other renderer — is in BLOCK_TYPE_META, not PALETTE_EXCLUSIONS, so it falsified no reason (0 hits in the exclusion range, control app:launcher = 1 in the same query). And declining the BlockTypeId docblock is the judgment I would have made: it claims membership of a local union, which is still true, and it covers global:notifications identically — which #6757 deliberately left. Rewording it would have applied a standard the card's own cited precedent rejects.

Every zero in the report carries a control that must hit in the same query. That is the standard.


Re-checking CI; I arm on green.


Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 1, 2026 02:56
@os-warren
os-warren added this pull request to the merge queueSep 1, 2026
Merged via the queue into main with commit 44ea62dSep 1, 2026
32 checks passed
@os-warren
os-warren deleted the claude/issue-7092-launcher-exclusion-reason branch September 1, 2026 03:10
Sign up for freeto 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.

finding(app-shell): PALETTE_EXCLUSIONS['app:launcher'] says "the app shell renders it, not a page" — PR #7091 makes that clause false

2 participants

@os-warren@claude