Repository source: docs/plans/archive/forte_compose_thread_step3_development_plan.md
Forte Compose Thread — Step 3: Development Plan
Stage 1
Goal: Get a working "New Thread" dialog on the board — reader can open it, fill it in, and successfully create a thread.
Dependencies: none (Step 2 approved)
Expected changes: new partial (e.g.
paned_board_new_thread_dialog.php) wrapping a<dialog>aroundthread_compose_form.phpin full (compact => false) mode, included fromforte_board.php; the "New" toolbar button loses itsdisabledattribute and gets adata-paned-board-newhook;paned_board_reader.jsgains a click handler that calls.showModal()on the dialog.Verification approach:
php -l; headless-browser test — click "New", confirm the dialog opens with Subject/Board tags/Body fields visible, fill in a real thread, submit, confirm the thread is actually created (write path unchanged from classic's own/compose/thread, so this reuses the existing writer). Redirect target after success is still classic's/threads/{new_thread_id}at this stage — expected and fixed in Stage 2, not a regression to chase down now.Risks or open questions:
thread_compose_form.phpships a "Clear fields" button and a second "Create thread anonymously" submit button, both of which are only wired up bybrowser_signing.js(data-action="clear-compose-fields"/submit-anonymous-compose"handlers live entirely inside that shared script). Per Step 2's decision not to load signing for this dialog (nodata-compose-root), both will render but be functionally inert-or-redundant: "Clear fields" does nothing, and the second submit button behaves identically to the first (both just post anonymously, since nothing else differentiates them without JS). This mirrors already-accepted dead-UI debt elsewhere in Forte (e.g. New/Refresh toolbar buttons) rather than introducing a new kind of problem — accepted, not fixed, but confirmed and recorded here rather than found later by surprise.
Canonical components/API contracts touched:
thread_compose_form.php(reused unchanged),forte_board.php/paned_board_reader.js(extended), new dialog partial (new, thin wrapper only).
Stage 2
Goal: Make a successful thread creation land back on Forte's board instead of bouncing to classic.
Dependencies: Stage 1 (need a working submission path to redirect from)
Expected changes:
handleComposeThreadSubmit()gainsreturn_tohandling via a newresolveComposeThreadReturnTo()helper (a sibling toresolveComposeReplyReturnTo(), not a modification of it — thread creation doesn't have an existing thread ID to whitelist against); on success, redirects to/fortewith the active tag preserved andselected={new_thread_id}appended, using the ID the server already generated ($result['thread_id']), so the client only ever needs to supplytag. The dialog's form gets a hiddenreturn_tofield populated by a small extension topaned_board_reader.js's existingcomposeReturnToUrl()-style logic (reused, called without a thread ID since none exists yet — produces/forte?tag=...with noselected).Verification approach:
php -l; live end-to-end test via the dialog — submit a new thread with a tag filter active, confirm the redirect lands on/forte?tag=...&selected={new_thread_id}, and confirm the board's existing, unmodifiedselected=restore logic inpaned_board_reader.jsalready picks it out correctly (no changes needed there — it already works off whatever thread ID is present in the freshly-rendered row list, new or old).Risks or open questions: none identified — this reuses the same
return_tomechanism already proven twice (forte_board_reply_restore,forte_identity_signing).Canonical components/API contracts touched:
Application.php(handleComposeThreadSubmit()extended,resolveComposeThreadReturnTo()new),paned_board_reader.js(extended).
Stage 3
Goal: Interaction and visual polish — dismissal, focus, and styling consistent with the rest of the paned chrome.
Dependencies: Stage 1 (dialog must exist)
Expected changes: an explicit Cancel control inside the dialog (native
<dialog>Esc-to-dismiss is free, but an on-screen close affordance is added for parity with the rest of Forte's UI, which has no keyboard-only affordances documented as a hard requirement); Subject field auto-focused on open;forte.cssgains<dialog>-specific rules matching the paned window's existing look (borders/chrome/typography), consistent with how.paned-compose-form/.paned-compose-statuswere styled for the Reply panel.Verification approach: headless-browser test — Cancel and Esc both close the dialog with no thread created and no other side effects; Subject field has focus immediately after
.showModal(); screenshot check that the dialog's styling reads as part of the paned window, not a generic browser dialog.Risks or open questions:
Repeated open/cancel/reopen cycles: since the
<dialog>stays in the DOM across opens (this isn't a fresh page load), confirm whether previously-typed field values should persist or reset on reopen. Default to whatever plain HTML gives for free (values persist since the DOM node persists) unless that reads as confusing in the live test — no explicit reset-on-open logic planned unless the test surfaces a real problem.
Canonical components/API contracts touched:
forte.css(extended),paned_board_reader.js(extended, dialog-open focus/Cancel handling).
Stage 4
Goal: Full regression check across the whole feature.
Dependencies: Stages 1-3
Expected changes: none (verification only)
Verification approach: confirm classic's
/compose/threadpage andboard.php's own inline compact composer are both byte-for-byte unaffected; confirm Forte's existing Reply flow (anonymous and signed) still works exactly as before (the New dialog shares no markup or JS state with the Reply panel beyond the toolbar/board scaffolding); confirm the board's tag filter and sort state survive a full create-thread round trip; confirm no console errors from the intentionally-inert Clear-fields/anonymous-submit buttons noted in Stage 1.Risks or open questions: none identified.
Canonical components/API contracts touched: none new — integration check across Stages 1-3.