POC (#68): git-backed handbook on Astro + Cloudflare — GitHub auth, review workflow, no CMS/DB - #72
Open
VibratingKoala wants to merge 94 commits into
Open
POC (#68): git-backed handbook on Astro + Cloudflare — GitHub auth, review workflow, no CMS/DB#72VibratingKoala wants to merge 94 commits into
VibratingKoala wants to merge 94 commits into
Conversation
From-scratch (no CMS) handbook on a single Astro SSR Worker over Cloudflare D1. - Auth: hand-written Google OAuth (AES-GCM session cookie, state-nonce CSRF, verified_email); DEV_LOGIN shim for local testing without Google. - ACL: middleware resolves role+groups from D1 per request (no identity bleed); one readableWhere predicate gates content; fail-closed; restricted hidden. - Sidebar shows the full handbook structure (collapsible, persisted); internal pages show a sign-in prompt; content stays access-controlled. - Editor at /admin (editor-role only): create/edit, draft/publish, live preview, double-submit CSRF, slug auto-generated from title, save confirmation banner. - Single VitePress-style design with light/dark toggle, nav bar, sidebar + on-this-page, OSBR logo. - D1 schema + migrate-doc seed from doc/*.md (generic personas, no real emails). Verified locally: pnpm check / lint / test (26) / build / guard; reader-ACL matrix, editor RBAC, CSRF, stored-XSS all confirmed against local D1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Sidebar: collapse sections by default, auto-expand current page's section; anon sees only public pages, logged-in users see all with "no access" badges - Right-side "On this page" TOC: collapsible h3 groups under each h2 - On-page pencil edit button for editors - In-content [[TOC]] now builds a nested list so h3 sub-items indent structurally; widen sanitize schema to keep class on ul/ol/li Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- "Back without saving" link returns to the admin page list - Delete button (own form, editor + CSRF gated) with a JS confirm; deletePage() removes the page and its group grants atomically - /admin/delete redirects to /admin?deleted=1 with a confirmation banner Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tions - Move Back (left) and Delete (right) into a top bar above the form - Wrap page metadata in a collapsible "Page settings" <details> (open by default) - Right-align Save draft / Publish at the bottom Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Style the editor-page <h1> (it was unstyled outside .prose); edit page heading now reads "Edit: <title>" - Replace the native confirm() with a styled <dialog>: "Delete this page? Are you sure… This cannot be undone." with Cancel / Delete actions Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wrap the markdown body + live preview grid in a collapsible <details> with one summary title, replacing the two separate per-pane titles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Drop the leadership group, the leader@/editor-leader@ personas, and their group membership from the seed generator - security-policy is now 'internal' instead of restricted-to-leadership - Regenerate seed (10 pages: 2 public, 8 internal; 0 groups), update POC.md The restricted/group-gated visibility level remains supported by the schema and ACL; it's just unused by the default seed now. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pins the primary actions while editing so they're reachable without scrolling whether the sections are collapsed or expanded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Back now uses history.back() to return to wherever the editor was opened from (the page or the list), falling back to /admin for direct loads. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Group pages under collapsible <details> per section with a count badge (drops the now-redundant Section column) - Style the admin-page <h1> (was unstyled outside .prose) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the create action onto the heading row as a primary button (the conventional spot), replacing the easy-to-miss text link. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- table-layout:fixed with shared column widths so every section's columns line up; right-align Visibility/Status/Actions - Add a Delete action per row that opens the same confirm modal as the editor and posts to /admin/delete (CSRF token now issued on the list page) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch from localStorage to sessionStorage and detect reloads via the Performance Navigation API: a refresh resets to the default (only the active page's section open); normal navigation restores opened sections. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previously two button systems (homepage/.btn pill vs editor/modal padding buttons) rendered at different heights and fonts. Now every button shares one base; Publish is primary (brand), Save draft secondary (alt). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Returns to the previous page (history.back), falling back to the handbook home for direct loads. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- searchPages() in db/pages.ts: case-insensitive LIKE on title/body, ANDed
with the existing readableWhere(visitor) ACL so results never leak a page
the caller can't read; title matches rank above body matches; LIKE
wildcards escaped
- GET /api/search returns { results } JSON, fails closed (503 + empty) on
DB error, sets Cache-Control private/no-store + Vary: Cookie
- Navbar search box with debounced fetch; results rendered via
createElement/textContent (no innerHTML on API data); plain-text snippets
- 6 new tests covering ACL (incl. draft visibility), ranking, empty query,
and bind ordering
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clearer for non-technical staff. Renames src/pages/admin -> edit-pages and updates every reference (navbar Editor link, reader edit pencil, New page, Edit/Delete links, save/delete/preview form actions + redirects, Back link). No redirect from the old /admin (clean break; nothing external links to it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Edit route keys on the page slug: /edit-pages/edit/<slug> (was numeric id). Adds getEditablePageBySlug(); Edit links, the reader edit pencil, and the save redirect all use the slug. - Rename the create route to /edit-pages/create (clearer than /new); heading now "Create page". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The native <datalist> popup is browser-rendered and can't be themed, so it looked out of place. Swap it for a custom dropdown styled to match the dark UI; still lets you pick an existing section or type a new one (free text). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Remove dead getPageVisibility() (never wired up) from db/pages.ts - Remove orphaned .pane-title CSS (panes lost their titles when the Content section became collapsible) - Style .nav-edit (the navbar "Editor" link had markup but no CSS) - Replace the "Back to handbook" inline style with an .admin-back class Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Visible label + help text only; the form field name and route slug are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both editor sections (Page settings, Content) now start collapsed. Open-state is remembered per tab (sessionStorage): a reload resets to collapsed, but navigating back restores whatever you had open — same model as the sidebar. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The identical history.back()/same-origin-referrer handler lived in both the editor and the Pages list. Move it to lib/ui/backLink.ts (wireBackLink). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The sessionStorage reload-reset / navigation-restore logic for <details> sections was duplicated (inline) in the sidebar and the editor. Move it to CollapsePersistence.astro, which renders the inline script (define:vars, so no flash) parameterised by selector + storageKey. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce src/content.config.ts (glob collection, fail-closed schema: missing visibility -> restricted, missing status -> draft) and generate src/content/pages/<slug>.md for all 10 pages via scripts/seed-content.mjs. Additive — nothing reads the collection yet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- lib/content/acl.ts: pure canRead() predicate (mirror of readableWhere, fail-closed) + search + snippet helpers — unit-testable without Astro - lib/content/pages.ts: getPageBySlug/getNavPages/getSidebarPages/ listAllPages/getEditablePageBySlug/searchPages over the collection - Visitor gains groupKeys (stable keys for the git ACL); groupIds kept until db/pages.ts is removed. Middleware resolves both via a groups JOIN. Not yet wired — readers still use db/pages.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Point the reader page, search API, sitemap, sidebar, and access.ts at lib/content/pages (git-backed) instead of lib/db/pages (D1). ACL verified live from frontmatter: anon sees only public, reader +internal, search + sitemap gated correctly. Editor pages still use D1 (Phase 3). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g everyone out) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…me="action" buttons)
form.action is shadowed by the draft/submit buttons (name="action"), so
fetch(form.action) hit /edit-pages/[object RadioNodeList] -> 404, shown as a
misleading "saving isn't enabled" popup. Use getAttribute("action") instead,
and give 404 its own error message so a routing failure can't masquerade as
an environment restriction again.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Section lists start collapsed on every viewport/load (was: open on desktop, mobile-only strip, persistence resetting on reload — three behaviors) - One user-facing name: navbar, dropdown, h1, tab title and back-link all say "Manage pages" (URL stays /edit-pages — stable identifier, not a label) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…uired-legend - "New pages in progress" gets a brand-tinted card (matches the draft badge hue) so active work reads differently from plain content sections - Resuming a draft shows ONE merged banner below Back/Delete instead of two green notices sandwiching it (draftResume prop on EditorForm) - "* required" legend removed — the asterisk convention needs no explanation (labels keep the tooltip, inputs keep the required attribute) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- One helper owns the delete-dialog rules both call sites shared (environment check FIRST on open and confirm; fetch-submit so failures pop up); the Pages listing injects row slug/title via onOpen - Remove .notice-muted / .preview-title (defined, referenced nowhere) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Delete was built for published pages (deletion PR -> review -> merge); a draft-only page 404'd and left its edit branch behind. Now delete.ts detects the draft-only case and calls discardDraft(): delete the edit branch (GitHub auto-closes any PR from it) — no review step, nothing was ever public. The listing shows a 'Draft discarded' banner; the client 404 message no longer misdiagnoses this as a session problem. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- submitForm shows the clicked button's data-busy label (Saving…/Submitting…/ Deleting…) and guards re-entry — a submit spans several GitHub API calls and a silent button invites double-clicks - save.ts redirects back to the EDIT page with the outcome banner (draft saved / submitted with PR link) instead of teleporting to the listing; the stale 'new page would 404' rationale predates resumable drafts - Listing keeps deleted/discarded/submitted banners (delete PRs still land there) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- prLinkFromParam (reviews.ts): one place validates ?pr/?submitted params and builds the GitHub link — was copy-pasted regex + URL template in two pages - Reviews page approve/reject: disable clicked button + data-busy label (Approving…/Rejecting…) — same dead-click/double-merge exposure the editor buttons had; runs after the reject-confirm so a cancel doesn't freeze it - pageshow reset: Back to a page frozen mid-submit reloads instead of showing a permanently disabled Submitting… button Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… the handbook
- /edit-pages/reviews/{n}: the proposed version rendered like a real page,
the currently-published version collapsible below for comparison, deletion
reviews called out, approve/reject in place (GitHub link kept as audit trail)
- getReview (reviews.ts): one open edit-PR, validated to our base + edit-branch
prefix — anything else 404s
- readPageAtRef (store.github.ts): page content at an arbitrary ref; readDraft
now reuses it (dedupe) with its unique-commits gate on top
- Reviews table: finally styled (.admin-table had NO css — browser-default
soup); Change title links to the internal page, GitHub demoted to a side link;
Page/Checks columns drop on mobile
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eriod) The workers.dev deployment is a proof-of-concept under evaluation, not the blessed staging of a production system. The label self-retires: the real domain renders no ribbon. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two action buttons + title + author can't share table columns at 375px, and a table's min content width drags the whole page horizontal. A review list is a list: text left, actions right, wrapping below on narrow screens. Removes the hour-old .admin-table styles it replaces. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Each review renders as a bordered card (same language as Manage pages
sections) instead of bare divider lines
- reviewLabel() strips our own PR-title scaffolding ('Submit "X" (slug)') so
the list and review page show just X; hand-made PR titles pass through
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pushes to the deployed branch (i68-handbook-poc now, main post-POC) that touch app/** test, build and deploy the Worker — this is what makes an approved submission actually appear on the site. Without the repo secret the run skips with a notice instead of failing every merge. keep_vars + the pinned SESSION KV id make CI deploys safe for dashboard config. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Snapshot of #73 at 23cc079 (doc/style-guide*.md), converted to content-collection format: frontmatter added (section Guideline, sorts 61-65, visibility internal — flip per page later if any should be public), leading h1 dropped (title renders from frontmatter). Slugs match the VitePress paths so Alex's cross-page links work unchanged. One deliberate divergence: the sk_live_-prefixed example string is replaced with an obvious placeholder so it can't trip secret scanners on this public repo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… tree) New optional 'parent' (slug) threads through schema, parse, serialize, the editor save round-trip (hidden field — a save must never strip nesting) and a recursive SidebarTree component (depth-capped so a frontmatter cycle can't hang the render; children of an ACL-hidden parent surface at top level rather than vanish). Style guides now nest: Development Guide > Style Guide > four languages, indented with the rule line like the VitePress original. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… open) Parents render as <details> nodes: the label link navigates (an <a> inside <summary> is its own activation target — no toggle), the chevron/row toggles, collapsed by default except the branch containing the current page. Joins the existing CollapsePersistence via data-section (n: prefix). Animated chevron, hover affordance, and the level rule line brightens under the pointer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lumns) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arent nesting, discard-draft The TEMPORARY-demo-auth section described a state that ended when the org GitHub App went live; replaced with current auth state + residual cleanup list. Owner checklist step marked done. Feature list gains the internal review page, draft-discard delete, and the parent frontmatter field. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closed
2 tasks
# Conflicts: # .github/workflows/run-tests.yml
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The POC no longer keeps hand-ported copies of the handbook content. The Astro collection now globs ../doc (the same files VitePress builds from); POC frontmatter (title/section/visibility/parent/sort) lives in doc/*.md where VitePress ignores it. Bodies keep their '# H1' on disk for VitePress — stripLeadingH1 on load, re-added on save, both sides in serialize.ts. Editor writes (GitHub store + local agent) target doc/. seed-content.mjs and its test retire with the copies. The one content edit: the HTML/CSS guide's fake-but-realistic api-key example string stays sanitized (public repo; secret scanners trip on sk_live_). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
llmstxt.org surface for AI agents (the standard-repository hook sends every org Claude session to the handbook). Unlike the old site's vitepress-plugin-llms (static, all-public — fine while everything is public), these are dynamic and run through the same canRead gate as the HTML pages: anonymous sees public only, fail closed, 404 for missing AND forbidden alike. Index links point at /<slug>.md so agents get raw markdown instead of scraping HTML. Verified live both ways: anon = 2 public entries, signed-in = all 15; cache flips to private,no-store + Vary: Cookie once a session exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-source refactor Without this, an approved editor merge (touches only doc/<slug>.md) would never redeploy the Worker and the published change would never appear. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…equisite Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ext H1
Four-agent review (reuse/simplification/efficiency/altitude), fixes:
- One excerpt rule (acl.excerpt) shared by search snippets + llms summaries;
one H1-restore rule (serialize.withTitleH1) shared by saves + .md endpoints;
one nav-grouping rule (acl.groupBySection) shared by sidebar + llms index;
one site title (lib/site.ts) shared by header brand, hero, llms header;
one CONTENT_DIR (serialize.ts) shared by the GitHub + local write drivers.
- llms.txt/llms-full.txt: shared llmsTextEndpoint factory; anonymous output
memoized per isolate (Workers don't edge-cache off Cache-Control alone, and
the anonymous result is a deploy-time constant). Dev exempt.
- run-tests.yml: the 6-line trigger condition now lives once, in a gate job.
- doc/security-policy.md led with a SETEXT H1 the strip regex can't see —
double title on the page, and a save would have prepended a second heading.
Converted to ATX, title aligned; new content.test.mjs enforces the
'# {title}' invariant over the whole doc/ corpus so this can't recur.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Custom Astro SSR app on Cloudflare Workers replacing VitePress/GitHub Pages, so the handbook can gate per-page reader access — which a static host can't do. No CMS, no database: content, access control, drafts, and publishing all live in git/GitHub.
Architecture
Sign in with GitHub (org GitHub App, live since 07-09). Each user's own token maps repo permission → role (
resolveRoleSelf), re-verified every ~10 min; no bot token in the App path (a classic-OAuth fallback with a bot token still exists but is slated for removal).Access model — reading vs capability are separate axes
Reading (2 tiers, roles play no part):
public→ anyone;internal→ any signed-in person. There is deliberately no finer tier — every signed-in user is a repo collaborator who could read the markdown source on GitHub anyway. Forbidden == not-found (404, no existence signal); fail-closed everywhere (unknown visibility → internal; malformed page files → tightest tier).Capability (from GitHub repo permission):
(The gate is the explicit-collaborator check (204/404), never
permission=="read"— GitHub reportsreadfor any user while the repo is public; regression-tested.)Publishing is git-native — no status field
Every commit is authored by the signed-in person (their GitHub App user token — no bot; GitHub refuses writes the moment push access is revoked). Concurrent edits 409 via blob sha; a branch holding unmerged commits is never force-reset (data-loss regression-tested); deletes are always review requests. The dashboard honors the
mainruleset fully — no self-approval, checks must pass, stale branches auto-refresh.Agent surface (llms.txt)
/llms.txt(index),/llms-full.txt(all readable content) and per-page/<slug>.md(raw markdown) — llmstxt.org endpoints for AI tooling (thestandard-repositorycode-quality hook points every org AI session at the handbook). Dynamic, through the samecanReadgate as HTML: anonymous sees public pages only, fail closed, forbidden == 404. Supersedes the old site's build-timevitepress-plugin-llmsoutput at cutover — same URLs, now ACL-aware. Note: the five style-guide pages must flip tovisibility: publicbefore cutover (the hook fetches anonymously) — pending sign-off.Test locally (no GitHub setup needed)
Real GitHub login is locally testable too: personal OAuth App (callback
http://localhost:4321/api/auth/callback),DEV_LOGIN=0— verified live.Staging & production
osbr-handbook);[env.staging]is inert with the Cloudflare adapter — see POC.md "Deploy to staging" for the mechanics (keep_vars, dashboard-managed vars,GITHUB_BRANCHPOC targeting). Non-prod hosts serve an environment ribbon +X-Robots-Tag: noindex; both retire automatically on the real domain (host-based).pnpm build && npx wrangler deploy).deploy-worker.ymlauto-deploys pushes to this branch /main(incl. content-onlydoc/**merges) once a repo admin adds theCLOUDFLARE_API_TOKENsecret — required for "approve & publish" to be self-serve.run-tests(required check) also fires onhandbook/*PRs — without that, editorial merges could never satisfy the ruleset. Adocs-buildjob keeps the legacy VitePress site green until cutover.Verified
# H1boundary, llms.txt builders, sanitizer XSS.astro check0/0/0, build, biome, both guards (incl. no-personal-emails).vitepress-plugin-llms) was run against the frontmattered doc/ files — no breakage, no frontmatter leakage.After this PR merges — the roadmap
At merge time
run-tests(add theci-testinglabel to trigger it on this branch)main(resolveBase()self-retires; GitHub retargets the existing openhandbook/*PRs too)One-time setup (parallel, any order)
[org admin]Org GitHub App created + installed on this repo (done 2026-07-09; residual cleanup — dropGITHUB_TOKEN,GITHUB_WRITE_ENABLED, revoke the personal demo PAT/OAuth app — tracked in POC.md)[repo admin]Add theCLOUDFLARE_API_TOKENrepo secret (company account; Workers Scripts:Edit + Workers KV Storage:Edit) so merges auto-deploy — until then every publish is a manual deploy[infra]DNS Phase 0: move theosbrjp.comzone from Route 53 to the company Cloudflare account (records copied exactly — MX/email double-checked; full runbook in POC.md). Blocks only the final domain flip[content]Flip the five style guides tovisibility: public(required by the standard-repository hook's anonymous fetch; pending sign-off)Production
handbook.osbrjp.comas the Worker's custom domain + switchOAUTH_ORIGIN— both sites stay up throughout; rollback = recreate one CNAMErelease.yml+ thereleasebranch; make the repo private (check for public forks first); retire the VitePress machinery (doc/.vitepress/, root deps,docs-buildjob) —doc/*.mdstays, it IS the contentGITHUB_BRANCHvar; set up staging isolation (separate worker) BEFORE routine post-cutover deploys; triage the stale pre-rewrite PRs (Add privacy policy page #65 Release Candidate 2026-07-09 20:09:51 +0900 #63 Fix Travel Planner Substantials #60 Modify On-boarding Guide #52 Add Accessibility Policy #44 Add a Main Visual to The Main Page #38 — POC: move handbook off VitePress to a headless CMS with reader RBAC + Google Workspace auth #71 closed 07-15) and the 35 Dependabot alerts that die with the old VitePress treeGITHUB_WRITE_ENABLEDstays wired as the editing kill switch in prod — one secret change disables editing instantly, readers unaffected.Closes #68.
🤖 Generated with Claude Code