Repository navigation
Description
Context
Desktop users reach the Console in a browser, and the product decision is to serve them there rather than ship a native desktop client. The Console is already half a PWA:
ui/apps/console/src/main.tsxgenerates and injects a web app manifest (generatePWAManifest);- it wraps the app in
MobileProviderwithpwa.enabled: true.
But nothing registers a Service Worker. @object-ui/mobile ships serviceWorker.ts and serviceWorkerSource.ts, and no code path calls the registration. As a result:
- the installed app has no offline app shell: a cold start with no network is a browser error page;
- the browser has no Service Worker to deliver Web Push to, so desktop users cannot get OS notifications even after the platform's push channel exists (Push channel: build the device registry routes (
/notifications/devices) and the APNs + FCM transport that ADR-0012 marks "MUST ship M1" #29041, spec slice spec(notification): the push device registry's protocol face and the push payload route (spec slice of #29041) #29067).
ADR-0012 §6 already lists web_push among the push device vendors.
Ask
- App shell caching.
- Register a Service Worker from the Console that precaches the app shell (the HTML entry and the hashed entry assets of the current build) and serves it when offline.
- Hashed chunks are versioned by the build, so the worker must drop the previous build's cache on activation.
- The worker must not cache API responses: data stays network-only, and session cookies are never stored by the worker.
- Update flow. When a new build is deployed, the next load picks it up without a hard refresh. Either the worker activates on next navigation, or the Console offers a "new version — reload" prompt. A stale shell must never pin users to an old build.
web_pushreadiness.- The Service Worker handles
pushandnotificationclick, and a click opens the payload's Consoleroute, which is the same field the mobile shell reads. - Registering a browser push subscription with the device registry is gated on Push channel: build the device registry routes (
/notifications/devices) and the APNs + FCM transport that ADR-0012 marks "MUST ship M1" #29041. Until then, the worker carries only the event handlers and their tests.
- The Service Worker handles
- Install affordance. Verify that the Console is installable in Chrome and Edge on desktop: the manifest is valid, it has icons at the required sizes, and its
start_urlandscopeare correct under/_console/. Branding from Setup applies to the installed app's name and icon (the manifest is already built from runtime branding).
Out of scope: a native desktop shell. If a concrete need appears later, the shape is a separate repository with a Tauri shell hosting the Console through the same window.ObjectStackNative bridge the mobile shell uses. The trigger needs are an installer or MDM distribution, tray residency, local file system or device access, or deep enterprise IM desktop integration. No card is opened for it until one of those needs is named.
Acceptance
- Playwright test: after one load, going offline and reloading shows the Console shell, not a browser error, and data calls fail visibly as offline.
- Deploying a new build is picked up within one navigation (test with two builds, or a mocked worker update).
- Unit tests cover the worker's cache-versioning, the API-bypass rule, and the
push/notificationclickhandlers (the click opens the route; a route that escapes/_console/is refused). - A Lighthouse or Chrome "installable" check passes on the served Console.
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: p2 ·
domain:ui·pm:queue· 2026-11-02T08:30Z⛔ Not a claim.
@object-ui/mobilealready ships, beside the manifestmain.tsxalready injects. The card states the product direction (serve desktop users in the browser, no native client).domain:ui, p2.web_pushdelivery path is a new push vendor in the device registry Push channel: build the device registry routes (/notifications/devices) and the APNs + FCM transport that ADR-0012 marks "MUST ship M1" #29041 builds (ADR-0012 §6). That half is a protocol addition: it rides Push channel: build the device registry routes (/notifications/devices) and the APNs + FCM transport that ADR-0012 marks "MUST ship M1" #29041's contract review, or splits to adomain:servercard. It does not land here.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Claim: PM loop round 11 · 2026-11-02T09:15Z
Session:
session_01QsR2jQhk3S1XeTLgXSkEMt(card thread, os-dev); dispatched by seat sessionsession_01QsR2jR7ZFkpqmm4U8S1f9yAccount:
zhuangjianguoBranch:
claude/project-thread-detgab(platform-assigned to the card thread; it can push only this branch)Worktree:
objectstack-issue-29155Domain:
domain:uiSeat:
domain:ui#4File surface:
ui/packages/mobile/src/serviceWorker.ts,serviceWorkerSource.ts, their tests and README, the registration inui/apps/console/src/main.tsx,ui/apps/console/vite.config.ts(worker emit and build id only),ui/apps/console/public/manifest.jsonif needed, a Playwright offline/update spec underui/e2e/, and a changeset (stop on breach; explain in the report)Container & model: L,
mode:thread,model: default tier (dispatch-gates --tier: no path-derived mandate),effort: defaultClause-②: no
Metadata-protocol: none
Responsibility:
desktop users run the Console as an installed browser app, but nothing registers a Service Worker, so a cold start offline is a browser error page and there is no worker for Web Push to reach |@object-ui/mobilealready ships the registration and a worker generator that no code path calls | every desktop user of the installed ConsoleThread-read: 6572618088
Premise: holds (at
origin/main, no file inui/apps/console/srccallsregisterServiceWorker)Serial constraints cleared: PR #29140 (#29039) touches
main.tsxand@object-ui/mobileand is in the merge queue; merge main after it landsGenerated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Claim amended · 2026-11-02T09:23Z · the Clause-② line is corrected; the branch, session and file surface are unchanged.
Clause-②: yes (widening)
Metadata-protocol: none
Reading (the seat's, from the dev's stop-and-tell before any push):
@object-ui/mobileis published, and its surface widens.ServiceWorkerSourceOptionsgains the optional membersstrategy('offline-first' | 'app-shell'),buildandnotifications.ServiceWorkerStrategy, the constantSERVICE_WORKER_CACHE_URLS_MESSAGEand the functioncacheLoadedResources(worker).getServiceWorkerSource()stays byte-identical, so every existing caller gets the same worker.That is a widening, and the changeset stays
patch. The review owed is the lane seat's ownTIER_DEFAULTrecord. Theapp-shellstrategy must keep API requests network-only (the brief's ruling), and that is what the review will check.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Claim amended · 2026-11-02T09:45Z · the file surface widens by two files; the branch, session and declaration lines are unchanged.
Clause-②: yes (widening)
Metadata-protocol: none
Measured by the dev on a default-base build served the way the CLI serves it, in real Chromium with CDP
Page.getInstallabilityErrors:consoleContentSecurityPolicyinpackages/cli/src/utils/console.ts) setsdefault-src 'self'and nomanifest-src. So the brandedblob:manifest thatmain.tsxinjects is refused, and the served Console is not installable onmaintoday (no-manifest,manifest-parsing-or-network-error).manifest-src 'self' blob:added, there are no installability errors, and the manifest carries the branded name,start_urlandscopeunder/_console/.Ruling: option A. Add the one directive at the producer, and keep Setup branding on the installed app. The widening is narrow: a
blob:manifest can only be minted by a script that has already passedscript-src.File surface (added):
packages/cli/src/utils/console.ts: themanifest-src 'self' blob:directive, plus its comment, which today saysdefault-srccovers the manifest.packages/cli/test/console.content-security-policy.test.ts(or wherever the CSP is pinned): a pin of the new directive.@objectstack/cli: patch.The PR body names the CSP change as a security-header widening, with the measurement above.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Claim amended · 2026-11-02T10:25Z · the file surface widens by one config line; the branch, session and declaration lines are unchanged.
Clause-②: yes (widening)
Metadata-protocol: none
Measured by the dev at head
f74b1548(aftermainmerged #29140):vite.config.tsimports the Service Worker generator from source,../../packages/mobile/src/serviceWorkerSource.ts, which is a dependency-free file.turbo run type-check --filter=@object-ui/consoleis red with TS6307, because the compositetsconfig.node.jsondoes not list that file. The vite build itself is green.Ruling: option A. Add that one path to the
includeofui/apps/console/tsconfig.node.json, beside the existing../../scripts/vite-*.tsentries. This is the same pattern the config already uses for source files it imports with an explicit.ts. Option B, importing from the@object-ui/mobileentry, is refused: it would load the mobile dist barrel, with its React components, into Node for the config, and the CI step "Build the Console for E2E" runsvite builddirectly, so it would depend on mobile's dist being built first.File surface (added):
ui/apps/console/tsconfig.node.json, the oneincludeentry only.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Claim amended · 2026-11-02T10:57Z · the file surface widens by three cache-input entries in the root
turbo.json; the branch, session and declaration lines are unchanged.Clause-②: yes (widening)
Metadata-protocol: none
Measured by the dev at
7ba4d977:pnpm --dir ui exec vitest run scripts/is red, with 40 cases acrossturbo-build-inputs,turbo-test-inputsandturbo-type-check-inputs. The cause is the one behind 6574807547. The console's build, test and type-check programs now readui/packages/mobile/src/serviceWorkerSource.tsfrom outside the package, and turbo does not hash that file. A change to the generator would then replay a stale cache. The gate's own fix text asks for the path in those three tasks' inputs.Ruling: add
"$TURBO_ROOT$/ui/packages/mobile/src/serviceWorkerSource.ts"once each to thebuild,testandtype-checktaskinputsin the rootturbo.json. Make no other change to that file. This follows the pattern the file already uses for$TURBO_ROOT$/ui/scripts/vite-*.ts. Importing the package entry instead stays refused (6574807547).check-governed-merges --testonturbo.json,ui/apps/console/tsconfig.node.jsonandpackages/cli/src/utils/console.tsreads NOT governed, 0 of 3.File surface (added):
turbo.json, those threeinputsentries only.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Claim amended · 2026-11-02T11:26Z · the file surface widens by one paths-filter row in
.github/workflows/ci.yml. This makes the PR governed: landing tier H (a human merge). The branch, session and declaration lines are unchanged.Clause-②: yes (widening)
Metadata-protocol: none
Measured by the dev at
e4e73277, which carries theturbo.jsonentries of 6575431847:pnpm check:ci-filter-parityexits 1. Every$TURBO_ROOT$build and test input must also be a row inci.yml's paths-filtercore:list.ui/packages/mobile/src/serviceWorkerSource.tsis not one yet. Adding the row turns the check green: 138 of 138 self-test assertions pass, and all 30 build inputs and 9 test inputs are covered.Option E was tried and refused on measurement. It would give
@object-ui/mobilea subpath export and havevite.config.tsimport it, which needs no root inputs. The root ui vitest config loads the console'svite.config.ts, so mobile'sdistmust exist wherever vitest or the console build runs. Test UI's "Run the ui tests" (16 shards) and Live E2E UI's "Build the Console for the live lane" build no@object-uidist. Every shard would abort at startup, and fixing that means editingci.ymljob steps. Dropping the build-time emit would leave the card undone.Ruling: add one row,
- 'ui/packages/mobile/src/serviceWorkerSource.ts', with a short comment, beside the existing ui build-input rows of thecore:filter. That is a filter row only: no job, step or trigger change. Amendments 6574807547 (tsconfig.node.jsoninclude) and 6575431847 (turbo.jsoninputs) stand.File surface (added):
.github/workflows/ci.yml, that onecore:filter row only.Landing:
check-governed-merges --prwill read GOVERNED (.github/workflows/**). The seat posts its review as the Tier H record, with the 维护者速读 pinned andneeds-user-decision. The PR merges by the maintainer's hand, or by an authorized approval and then the seat. The seat does not flip it ready, enqueue it or arm auto-merge before that.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
os-dev-report
{ "issue": 29155, "status": "done", "branch": "claude/project-thread-detgab", "pr": "https://github.com/objectstack-ai/objectstack/pull/29326", "head": "b5658de9d5d4778a2531dcaf245cdaf4faa15fc3", "session": "session_01QsR2jQhk3S1XeTLgXSkEMt", "premise_still_valid": true, "summary": "The Console's production build emits a build-versioned app-shell service-worker.js (new 'app-shell' strategy in @object-ui/mobile's generator; default 'offline-first' output byte-identical to main) and registers it under /_console/, never in the native shell, where an earlier one is unregistered. /api/ and out-of-scope requests are network-only; navigations network-first with the cached entry offline; hashed assets cache-first; activation deletes this scope's older build caches. push/notificationclick handlers open only in-scope in-app routes; no subscription. The CLI console CSP gains manifest-src 'self' blob: (seat ruling A), making the served Console installable.", "tests": "@object-ui/mobile vitest 115/115 (25 new); @objectstack/cli CSP suite 50/50; console tsc + vite build green; turbo type-check mobile+console 72/72; Playwright chromium 66 passed / 2 skipped pre-existing at e4e73277, incl. 2 new console-service-worker cases (offline reload fromServiceWorker + auth-server-unreachable + API TypeError + no /api/ cache; one-navigation update with only the -next cache left). Ablations, each restored byte-identical: API bypass removed -> 2 red; click-route scope check removed -> 2 red; activation deleting all caches -> 1 red. Installability CDP: before manifest-parsing-or-network-error + no-manifest, after none (installability-check.png).", "gates": "dispatch-gates --ran: 98 derived, 98 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0 (65 at e4e73277, 33 ci.yml-row families at b0ef42a0, ci-filter-parity/type-check-debt/sdui-manifest/ui vitest scripts/ 169 files 4903 tests re-run at b5658de9 after merging main a19759ac). PR-body gates with GITHUB_EVENT_PATH: check-changeset-no-major --base origin/main 0, check-adr-0087-registration 0, check-closing-keyword-parity 0, check-partof-closing-keyword 0. check-governed-merges --pr 29326: exit 3 GOVERNED, tier H, one hit .github/workflows/ci.yml, 1119 lines. CI check-runs: pending at report time.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "line_budget": "+1090/-29 across 14 files, under 3000", "files_changed": [ ".changeset/29155-console-desktop-pwa.md", ".github/workflows/ci.yml", "packages/cli/src/utils/console.content-security-policy.test.ts", "packages/cli/src/utils/console.ts", "turbo.json", "ui/apps/console/src/main.tsx", "ui/apps/console/tsconfig.node.json", "ui/apps/console/vite.config.ts", "ui/e2e/console-service-worker.spec.ts", "ui/packages/mobile/README.md", "ui/packages/mobile/src/__tests__/serviceWorkerSource.test.ts", "ui/packages/mobile/src/index.ts", "ui/packages/mobile/src/serviceWorker.ts", "ui/packages/mobile/src/serviceWorkerSource.ts" ], "deviations": [ "Clause-② amended by the seat to yes (widening); Metadata-protocol none; both copied verbatim.", "Surface amendments granted by the seat: CLI CSP + pin (6573999866), tsconfig.node.json include (6574807547), turbo.json three inputs (6575431847), one ci.yml core: filter row (6575983994). Option E (subpath export) measured failing and dropped.", "The e2e serves dist itself with an injected base tag instead of vite preview (no base tag there, app leaves the worker scope).", "PR is Tier H (ci.yml): left draft, not flipped or enqueued; landing is the seat's after the user decides." ], "mcp_calls": "1 — mcp__github__pull_request_read (get #29326); no write tools", "api_writes": "9 — git push x7 to claude/project-thread-detgab; pr_create x1 via fleet-write relay (#29326); os-dev-report comment x1 via post-stamped on #29155", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Seat review · 2026-11-02T12:58Z · PR #29326 at
55e3e5e5: contract review (CONTRACT_REVIEW_TIER, director seat) PASS 6577669391, the seat'sTIER_DEFAULTPASS 6577635035, and ACCEPT → PR #29326 comment 6577690271. Tier H (ci.yml):needs-user-decisionis on the PR, and review is requested fromos-zhuangandhotlong. The PR stays a draft until an authorized approval or a maintainer's merge. The director's should-fix is filed as #29361.Generated by Claude Code