Repository source: docs/plans/viewer_nav_prepaint/viewer_nav_prepaint_step3_development_plan.md
Debug mode: enabled
Effort study: railspike-effort / v3/viewer_nav_prepaint; ledger. Standing P2 enrollment.
Extended estimates: enabled
Approval: Step 2 v3 approved 2026-10-29; Step 3 (this revision) approved 2026-10-29 (“Approved Step 3”, given before Step 2 v3 and confirmed standing once it was approved). Step 1 skipped. Waiting tab: option A chosen 2026-10-29 (count in shared HTML; follow-ups in todo.txt).
Cycle estimate: 0.50
Revised from 0.40 for scope v3 on main c5783108.
Completion Contract
Normal entry/outcome: approved member browses Board → thread → About → Users → Tools with high-traffic mode on/off; Messages (main nav / phone tab bar) and Invite (account menu) have their final visibility and position at first paint, in the app shell and the forum header. Anonymous/unapproved visitors have neither and no gap. Server header HTML is identical across viewers for the same route/configuration; qdb omits Invite and disabled messaging omits Messages. With Stage 4,
/users/tabs follow the same rule.Recovery: existing authentication/status responses, sign-out and identity changes update local approval and the menu in place; unchanged status causes no visible mutation. Stale approval never grants route access. Unread state works on cached pages without changing button size/caption.
Release: real-browser first-paint and transition evidence, phone/desktop overflow and Word97/Chicago/Midnight icon checks, focused regressions and updated UI audit. Include standalone layout where it renders affected UI. Deployment/physical-device claims require separate evidence; no deployment is authorized.
Key Risks
High risk: incorrect identity memory could display another identity's links. Bind approval to the identity
PrivateSiteAuthalready snapshots; ignore responses that fail its current-identity check, clear onforum:identity-changed, sign-out and 401. Network failures are not revocations. Server authorization unchanged.High risk: late hiding. The hide rule must be in the inline critical CSS and beat the tab bar's
display: flex; the show decision must run in the existing head script before body paint, with hidden as the fallback for missing/blocked storage. Validate on phone before Stage 3.On-demand messaging scripts must not resize or re-caption Messages when they arrive, and must not load for other viewers. No new status polling or server personalization.
Missing prior approval memory cannot prove membership at first paint: stay hidden until an existing response establishes approval, and record that as a real state change. No-JS members see no Messages/Invite (accepted in Step 2); direct routes keep authorization.
Stage 1 — Remember approval for the current identity
Estimate: 0.08 Revised from 0.10: sign-in now has a current-identity check and change events to build on.
Goal: existing sign-in/status/sign-out flows maintain accurate client approval memory.
Dependencies: none; establish identity binding and response-race behavior before Stage 2.
Expected changes: record approval from
authenticate_identityapproved=(inPrivateSiteAuth, after its current-identity check) and fromauth_status200/401 (auth_navigation.js); clear onforum:identity-changed,clearBrowserIdentityand cross-tabstoragechanges. One small client state contract next to the existing identity keys; no endpoint/cookie changes. Dispatch one event when the remembered state changes.Verification approach: focused auth/signing tests for approved/unapproved, 401, transport failure, missing/blocked storage, a delayed old-identity response and background (in-place) sign-in; confirm existing sign-in/recovery still works.
Risks or open questions:
auth_statushas no identity field; capture identity when requesting and drop mismatched completions.Canonical components/API contracts touched:
private_site_auth.js,auth_navigation.js,browser_signing.js; existing auth responses unchanged.Estimate details: S1/v2 stage work; driver is lifecycle wiring across three scripts, uncertainty is the background sign-in paths.
Stage 2 — Render a static header with pre-paint visibility
Estimate: 0.15 Revised from 0.12: two render sites (main nav and account menu), the nav fragment, critical CSS and tab-bar layout.
Goal: returning members see consistent Messages/Invite on live and cached pages, with real status transitions applied in place.
Dependencies: Stage 1 identity/race checks pass.
Expected changes:
navItemsalways emits eligible Messages/Invite (hidden by default) without viewer tokens;nav.phpandapp_header.phpmark them for the pre-paint rule; a hide rule in the inline critical CSS that wins over tab-bar/nav-link display; the head script sets one root attribute from Stage 1 state; same-visit updates toggle it.navigationStateand the feature-flag nav refresh keep working, including the account-menu Invite it does not re-render today. Checkstandalone_layout.phpparity; preserve active-section, qdb, members-only and feature-flag behavior.Verification approach: update LocalAppSmokeTest, HighTrafficTest and AppNavigationPlacementTest assertions; compare same-route header bytes for anonymous/unapproved/approved viewers; browser-check initial visibility, phone tab widths and unchanged-status stability before advancing.
Risks or open questions: themes may override hiding or leave gaps in the tab bar or account menu; verify computed styles and positions, including no-JS/storage-failure fallback.
Canonical components/API contracts touched:
TemplateRenderer(navItems,navigationItems,navigationState, critical CSS),nav.php,app_header.php,layout.php,standalone_layout.php,site.css, Stage 1 client contract; reuse icon mappings.Estimate details: S2/v2 stage work; driver is shared rendering/timing across two headers, uncertainty is theme and tab-bar interaction.
Stage 3 — Load messaging on demand and keep unread working
Estimate: 0.12 Revised from 0.08: messaging scripts now have to load from the client, since the server no longer knows who will see Messages.
Goal: unread state and messaging availability work through the static menu on cached and live pages, and across sign-in/out, without resizing Messages.
Dependencies: Stage 2 header and initial-paint checks pass.
Expected changes: stop adding messaging scripts only when Messages was rendered; load them for remembered-approved members (reusing the script list
navigationStatealready returns), and not for others. Resolve the unread viewer from local identity with existing normalization; start/stop unread with eligibility, invalidate stale responses on identity change, keep retry and messaging-disabled behavior.Verification approach: existing unread UI/API/live-navigation checks on cached and live pages; slow script and delayed unread responses, identity switch, sign-out, retry and feature-flag transitions; assert fixed caption/dimensions and no messaging downloads for anonymous visitors.
Risks or open questions: username/token mismatch can reject valid unread payloads; verify against the existing API viewer contract before removing markup coupling. Pages that list messaging scripts for their own reasons (Messages itself) keep them.
Canonical components/API contracts touched:
TemplateRendererscript selection,private_message_unread.js,private_message_availability.js; unread API unchanged.Estimate details: S3/v2 stage work; driver is script loading and request lifecycle, uncertainty is existing availability listeners.
Stage 4 — Users tabs follow the same rule
Estimate: 0.05 New in v3.
Goal:
/users/shows Members / Waiting from first paint for returning approved members and no tabs for others, cached or live.Dependencies: Stage 2 pre-paint attribute in place.
Expected changes: the Users page always renders
users_tabs.php, hidden by default via the Stage 2 rule; the Waiting (N) count comes from the shared render (option A)./users/pendingstays approved-only.Verification approach: update the Users browser test and Users page assertions for anonymous/unapproved/approved, cached and live, phone/desktop.
Risks or open questions: the count can go stale between static rebuilds and is visible in page source; both are tracked in
todo.txt, outside this cycle. Note what refreshes the static copy when someone registers.Canonical components/API contracts touched:
ProfilePageController::directory,users.php,users_tabs.php,tests/browser/users_profile.mjs.Estimate details: S4/v2 stage work; small template change on Stage 2's mechanism.
Stage 5 — Verify the full browsing and recovery flow
Estimate: 0.10 Unchanged from original S4 0.10.
Goal: demonstrate the reported cached/live inconsistency is resolved and record release limits.
Dependencies: Stages 1–4 verified and committed.
Expected changes: extend existing browser fixtures/checks; update
ui_copy_audit_checklist.md, cycle summary/debug log and study ledger.Verification approach: Board → thread → About → Users → Tools, both traffic modes, desktop/phone, app shell and forum header, and Word97/Chicago/Midnight; record first-paint visibility and nav positions through load with delayed responses, not just final screenshots. Check sign-in/out (including background sign-in), approval/revocation, identity switch, empty storage, unchanged refresh, no-JS, qdb, disabled messaging, unread failure/retry, account menu open/closed, keyboard reachability and overflow. Run affected existing PHP/browser suites and inspect shared HTML for viewer leakage.
Risks or open questions: layout-shift metrics alone miss pre-observer flashes; install observation before page scripts and pair it with rendered evidence. Unresolved required states block completion.
Canonical components/API contracts touched: existing smoke/high-traffic/auth/unread/users tests and browser fixtures; approved documentation only beyond application scope.
Estimate details: S5/v2 stage work; owns shared acceptance/closeout, uncertainty is fixture coverage; earlier stages own their focused verification.
Inventory and study checkpoint
Dependency delta: zero additions/upgrades, packages, manifests or copied components; use existing tools. No schema, API, authorization, cookies, Apache or deployment changes. Keep planning uncommitted; after Approved Step 3, create a feature branch from current main, organize this cycle and make its planning commit, then one verified summary-bearing commit per stage.
Original checkpoint (v2): Step 3 handoff, 2026-10-27 UTC; scope v2, original baseline 2bfab452, R1/P2. Codex single exposed rater. Whole-cycle 0.40 reaffirmed; stage-work originals S1–S4/v1 0.10 / 0.12 / 0.08 / 0.10 (Stage 1, 2, 3 and verification), never summed. Four stages planned, none complete.
Revision checkpoint (v3): 2026-10-29T18:37:06Z, Step 3 revision; scope v3, starting revision c5783108, R1/P2; all three frozen R1 hashes verified. Claude Code single exposed rater, exposed to the earlier Claude/Codex scores and to current renderer, header, auth, unread, script-loading, tab-bar and Users source. 0.50 whole-cycle projection (option A; 0.45 had been the without-Waiting alternative), revised from 0.40: new understanding of the shell's two render sites and conditional script loading, plus the optional Waiting scope. Stage-work S1–S5/v2: 0.08 / 0.15 / 0.12 / 0.05 / 0.10, separate judgments, never summed; v1 originals above are retained. Five stages planned, none complete; no implementation, measured work or accepted outcome.
— Codex · 2026-10-27 UTC
— Revised by Claude Code · 2026-10-29 UTC · Model: claude-opus-5-5 (all stages rebased on c5783108; Stage 4 new)