Repository source: docs/plans/reply_title_live_update/reply_title_live_update_one_shot.md
Cycle: v3/reply_title_live_update; Mode: one-shot; Record: this file; Index: plans index; Predecessor: v3/reply_title_directive
Debug mode: enabled (project marker .railspike_debug_on)
Effort study: railspike-effort / v3/reply_title_live_update — host ledger; standing enrollment under protocol P2
Extended estimates: enabled (no real alternatives or work units; no unit estimates)
Cycle estimate: 0.15
Status: implemented and verified locally; next permitted action: completion commit on feat/reply-title-live-update (merge and push need the user's instruction)
Original request
"it's working great. one thing i noticed is that when i retitle a thread, the card at the top of the page does not update with the new title until i reload the page. i think this is to be expected. can you please estimate complexity of improving this" — followed, after the estimate and the offer to run it as a one-shot, by "go ahead please" (2026-11-03).
Authorization
User instruction "go ahead please" on 2026-11-03, replying to the offer "Say the word and I'll run it as a one-shot" for the scope estimated at about 0.15. Scope: after a reply that retitles its thread is accepted through the inline composer, update the root card heading, the browser tab title and the root card's "Title set by" segment in place, and the Forte pane subject when posted from the pane. Exclusions: no change to records, schema, authority or flags; a rejected directive carries nothing in the response and shows no change or explanation (agreed in the predecessor). Merge and push are not covered by this authority.
Outcome and work type
Application change. Normal entry: the thread page's inline reply composer (and the Forte pane composer where it shares the same delivery code). End-to-end outcome: the accepted-reply response names the thread's effective title and the title author's name when, and only when, the reply wrote a subject record; the page updates heading, tab title and signals segment without a reload, and a reload shows the same. Recovery: if the response lacks the fields or the client code fails, the page simply keeps the old heading until reload, which is today's behavior; nothing is lost.
Approach and risks
Server:
LocalWriteService::createPreparedPost,createReplyandcreateSignedPostalready know when a subject record was written; addthread_subjectandthread_subject_author(username) to their results in that case only. The finalize route passes the writer result through, so no route change is expected.Client: in
public/assets/reply_continuation.js, on an accepted result carrying those fields, update.thread-root-card h1,document.title(preserving the site suffix), and the root signals line's[data-signal-tag="title"]segment (create it if absent, mirroringappendSignalActor); update.paned-content-subjectwhen present.Risks: the finalize JSON parser may whitelist fields (check
parsePreparedPostJsonResponse); a reply accepted through the retry/confirm path has no fresh response and keeps the old heading (acceptable, reload shows it). Mitigation: only act on fields present in a fresh accepted response.
Concepts touched
Thread title — changed: scenario gains the in-place heading update.
Post presentation — used unchanged.
Verification and recovery plan
PHP: extend
LocalWriteServiceTitleDirectiveTestto assert the new result fields on an authorized reply and their absence on a rejected one.Browser: extend
tests/browser/title_directive_replay.mjsso the author's retitle is asserted on the page before any reload, then after reload.Focused suites touched by the change;
git diff --check; dependency delta zero.
Starting revisions and branch
Host main at 699ebd26 (merged predecessor, schema 46); branch feat/reply-title-live-update.
Study history
2026-11-03T19:00:27Z intake: whole-cycle projection 0.15, scope v1 as above, R1 (hashes verified in the predecessor cycle today), P2, Claude Code single exposed rater (model claude-fable-5-1). Exposure: the estimate was formed after reading the predecessor's write path and the signals script, but before reading the inline continuation module and finalize route; disclosed, not pristine. Execution entry: 0.15 reaffirmed after reading those two.
Dated execution history
2026-11-03T19:00:27Z: branch created from
main; record created; implementation begins.2026-11-03T19:07:37Z: approach change (simplification within scope, not a scope change): the accepted-reply render pass in
public/assets/browser_signing.jsalready fetches the thread page (/threads/<id>?created_post_id=…) and reconciles earlier cards from it. A newsyncThreadRootFromDocument(doc)copies the root card heading, the document title and the root signals line from that server-rendered document when they differ. No server response fields, no PHP change; the plannedLocalWriteServiceTitleDirectiveTestextension is therefore not applicable.2026-11-03T19:07:37Z: Forte: the continuation module is deliberately not bound under
/forte, so the pane composer is outside this path; whether the pane re-renders its subject after posting was not verified. Recorded as out of scope for this cycle, not as done.2026-11-03T19:07:37Z:
tests/browser/title_directive_replay.mjsnow asserts, before any reload, that the root heading, tab title andTitle set bysegment update for the author's retitle and for the member's retitle after the flag change.
Outcome
Delivered: after a reply that retitles its thread is accepted through the inline composer, the thread page's heading, tab title and root signals line update in place; a reload shows the same. Rejected directives change nothing on the page, as agreed. Not delivered: Forte pane (outside the continuation path, unverified). Recovery unchanged: if the render fetch fails the page keeps the old heading until reload, which was the previous behavior.
Verification
node tests/browser/title_directive_replay.mjs: ok (2026-11-03T19:07:37Z), with the new pre-reload assertions.node tests/browser/inline_reply_link.mjs,signed_reactions.mjs,post_signals.mjs: pass.php tests/run.php AccountKeyPageHooksTest LazyComposeSigningTest PostRetractionScriptTest BrowserSigningNormalizationTest PrivateMessageComposerTest WriteApiSmokeTest LocalAppSmokeTest(every PHP test referencing the script): 466 run, 463 passed; the 3 failures are the known baselineLocalAppSmokeTestones.git diff --checkclean (see commit). Dependency delta zero. Full suite not rerun for this cycle; it was run on the merged predecessor tree the same day (see that cycle's ledger entry).Limits: Chromium only; not Firefox/WebKit, no physical phone, not production.
Study closeout
Intake 0.15; execution entry 0.15 reaffirmed; retrospective scope assessment 0.10: the client-only approach removed the planned server and PHP-test work. Single exposed rater Claude Code (model claude-fable-5-1), R1/P2. Maintainer comfortable-fit judgment and acceptance pending; no accepted-count increment.
Commit status
Completion commit 8d31e736 on feat/reply-title-live-update (confirmed with Git after the commit; this line was added in the record-only handoff commit). Merge and push are not covered by this cycle's authority.
— Claude Code · 2026-11-03 UTC · Model: claude-fable-5-1