Railspike Step 3: Development Plan
File: docs/plans/mobile_compose_keyboard_step3_development_plan.md (in the v3 repo, uncommitted)
Debug mode: enabled
Effort study: `v3/mobile_compose_keyboard` · [ledger](./effort_estimation_trial.md#cycle-v3mobile_compose_keyboard)
Extended estimates: enabled
**Cycle estimate: 0.40**
## Completion Contract
- Normal entry: New post in the bottom tab bar, `/compose/thread`, on a phone.
- End-to-end outcome: with the keyboard open, the title (see Q2), text area and Post row fit above the keyboard. The caret line and Post stay visible while typing; tapping Post creates exactly one thread.
- Recovery: a validation, signing or network error keeps the draft and shows the message with Post still visible for retry. If the space is too small (zoom, long error, rotation), the form falls back to normal page flow and stays scrollable.
- Release: the automated checks in S3 pass; device acceptance on Ilya's iPhone SE in Firefox and Safari is recorded in S4. Deployment is outside this cycle.
## Key Risks
- **High risk:** keyboard detection on iOS Firefox. `reply_viewport.js` assumes the keyboard is up when the visible height drops below 75% of the tallest seen. If Firefox reports heights differently, nothing docks. Early validation: the S2 build logs `visualViewport` heights in a debug query mode, and Ilya checks once on the SE before S3. Mitigation: the S1 fallback layout already keeps Post closer, and docking only applies when detection succeeds.
- Shared docking code: the reply composer depends on the same script, and `v3/composer_anchor_exit` (Step 1, unapproved) also plans to touch it. Early validation: run all `reply_viewport*`, `reply_continuation` and `reply_compose_focus` browser tests after S2. Mitigation: add a second hook without changing reply behavior.
- Shared composer partial: reordering tools changes the paned new-thread dialog too. Validation: `compose_disclosure`, `compose_layout_stability`, `composers` tests and a desktop paned-dialog check in S3.
## Stage 1
**Estimate: 0.08**
- Goal: the dedicated form has the new order and a phone-friendly flow, with or without JavaScript.
- Dependencies: none.
- Expected changes: in `composer.php`, render the tools row (tags, event) after `.compose-form-actions` for the full composer only. In `compose.css`, under 640px on `.compose-page`, drop the reserved hidden image-picker footprint that sits between the body and Post (keep it on wider screens, where it prevents layout shift), and shorten the default body height toward the compact form's.
- Verification approach: render the partial in PHP for full and compact modes and check element order. Headless WebKit at 375 x 667 and 375 x 300 in all 13 themes: Post's distance from the top of the body shrinks, with no horizontal overflow. `compose_layout_stability` and `compose_disclosure` pass.
- Estimate details: driver: shared partial and the hidden-footprint rule; uncertainty: low.
- Risks or open questions:
- Impact: removing the footprint could reintroduce the image-eligibility shift the rule prevents.
- Early warning / validation: `compose_layout_stability` on phone widths.
- Mitigation: keep the footprint but place it after Post instead of removing it, if the test fails.
- Canonical components/API contracts touched: `templates/partials/composer.php`, `public/assets/compose.css`; form field names and submit contract unchanged.
## Stage 2
**Estimate: 0.15**
- Goal: when the keyboard is up on `/compose/thread`, the form docks above it exactly as the phone reply panel does.
- Dependencies: S1.
- Expected changes: in `reply_viewport.js`, accept a neutral `data-phone-composer` form hook alongside `data-phone-reply`, with the panel found the same way (`[data-reply-panel]`). Reply-only parts (`forum:reply-sent` reading mode) stay reply-only. `compose_thread.php` marks its card as the panel and its form with the hook; the paned dialog does not opt in. In `compose.css`, while docked, the body flexes, collapsed tools are hidden (an open tool stays and the existing fit checks fall back to page flow if it doesn't fit), and the title follows Q2. A `?debug-viewport=1` mode shows the measured heights for the device check.
- Verification approach: fake `visualViewport` harness (as in `reply_viewport.mjs`) at 375 x 300 with the compose form: docked, editor at least 80px, editor and actions don't overlap, actions inside the visible area, caret and selection kept, an 80-line draft, error feedback and the open "..." menu all fit. All existing reply viewport tests pass unchanged.
- Estimate details: driver: generalizing shared script hooks without reply regressions; uncertainty: medium (iOS Firefox detection).
- Risks or open questions:
- Impact: a reply regression would break the most-used composer.
- Early warning / validation: the reply tests listed in Key Risks, run before the compose tests.
- Mitigation: additive hook; no changes to reply thresholds or timing.
- Canonical components/API contracts touched: `public/assets/reply_viewport.js` (docking contract: `[data-reply-panel]`, `--reply-panel-*` variables, `.reply-panel-active`), `templates/pages/compose_thread.php`, `public/assets/compose.css`.
## Stage 3
**Estimate: 0.12**
- Goal: lock the behavior in with automated checks across themes and composers.
- Dependencies: S2; Ilya's device height check from S2.
- Expected changes: add `tests/browser/compose_thread_viewport.mjs`: 13 themes at 375 x 667 and 375 x 300 (fake keyboard), WebKit and Chromium. It checks order (tags after Post), docking, no overlap, no horizontal overflow, short and long drafts, error retry, and no-JavaScript flow. Run the existing composer, disclosure, layout stability, reply viewport, tab bar and image tests.
- Verification approach: all new and listed existing tests pass, with results recorded in the Step 4 summary.
- Estimate details: driver: 13-theme matrix and fake-viewport setup; uncertainty: low (the harness already exists for replies).
- Risks or open questions:
- Impact: icon themes (Word 97, Chicago, Midnight) have fixed status bars that eat vertical space.
- Early warning / validation: the theme matrix at 375 x 300.
- Mitigation: the reply panel's reading mode already measures a fixed `.site-status-bar`; apply the same measurement to the docked height if these themes fail.
- Canonical components/API contracts touched: browser test harness in `tests/browser/`; no product contracts.
## Stage 4
**Estimate: 0.07**
- Goal: replay the reported failure on the real phone and close out.
- Dependencies: S3; Ilya with the iPhone SE.
- Expected changes: Ilya opens New post in Firefox and Safari, types a short and a long draft with the keyboard open, and posts once (a test thread he can retract). Record device, iOS and browser versions and the result in the Step 4 summary. Add a `docs/concepts/composer.md` entry for the shared composer and its phone docking, and record the compose page's phone verification in `docs/plans/ui_copy_audit_checklist.md`. Add the closeout checkpoint to the effort ledger.
- Verification approach: the device result itself; headless results don't count here. A failure goes back to S2 with the observed heights.
- Estimate details: driver: waiting on the device check; uncertainty: medium (unknown device result).
- Risks or open questions:
- Impact: a device-only failure after automated tests pass.
- Early warning / validation: the S2 height check catches detection problems early.
- Mitigation: the S1 fallback still improves the page; record the result and the follow-up honestly.
- Canonical components/API contracts touched: `docs/concepts/` (new composer entry), effort ledger.
## Concepts touched
- Composer (new entry `docs/concepts/composer.md`, S4): changed (order, phone docking for the dedicated page).
- Phone reply panel docking (described within the composer entry): extended with a second hook, reply behavior unchanged.
- App shell / bottom tab bar: used unchanged.
## Open questions for Ilya (defaults in brackets)
- Q1: while the keyboard is up, can the collapsed tags/event row hide until the keyboard closes? [yes]
- Q2: should the title stay on screen while docked, or hide when it's empty and the body has focus, to give the SE about 50px more writing space? [stay, per the clarification]
- Q3: should the paned new-thread dialog get the new order only, without docking, since it's a wide-screen layout? [yes]
## Enhancement Requests
- **Request:** Word 97 / Midnight phone overflow and Sticker / Arena Post padding at 375px. **Disposition and reason:** separate work; different cause. **Approval status:** pending. **Follow-up:** separate cycle on request.
## Effort snapshot
2026-10-31T06:15Z · Step 3 handoff · cycle `v3/mobile_compose_keyboard` · scope v1 unchanged · baseline `63f0849691902e665506ad4f23f374353a32ae78` · R1 (three hashes verified) · P2 · Claude single exposed rater (also inspected `reply_viewport.js`, its tests and `compose.css` for this plan) · **0.40 projected total, reaffirmed**. Stage work S1 0.08, S2 0.15, S3 0.12, S4 0.07; independent stage judgments, not summed into the cycle value. Reason unchanged: reuse of the existing docking script plus a reorder; the theme matrix and the device check keep it above the smallest references.
## Approval
Step 2 approved. Answer Q1–Q3 if the defaults are wrong, then reply "Approved Step 3" and I'll start on a feature branch.
*— Claude · 2026-10-31 UTC · Model: Claude Opus 5.5*
#meta #bug #railspike
#
File: docs/plans/mobile_compose_keyboard_step3_development_plan.md (in the v3 repo, uncommitted)
Debug mode: enabled
Effort study: `v3/mobile_compose_keyboard` · [ledger](./effort_estimation_trial.md#cycle-v3mobile_compose_keyboard)
Extended estimates: enabled
**Cycle estimate: 0.40**
## Completion Contract
- Normal entry: New post in the bottom tab bar, `/compose/thread`, on a phone.
- End-to-end outcome: with the keyboard open, the title (see Q2), text area and Post row fit above the keyboard. The caret line and Post stay visible while typing; tapping Post creates exactly one thread.
- Recovery: a validation, signing or network error keeps the draft and shows the message with Post still visible for retry. If the space is too small (zoom, long error, rotation), the form falls back to normal page flow and stays scrollable.
- Release: the automated checks in S3 pass; device acceptance on Ilya's iPhone SE in Firefox and Safari is recorded in S4. Deployment is outside this cycle.
## Key Risks
- **High risk:** keyboard detection on iOS Firefox. `reply_viewport.js` assumes the keyboard is up when the visible height drops below 75% of the tallest seen. If Firefox reports heights differently, nothing docks. Early validation: the S2 build logs `visualViewport` heights in a debug query mode, and Ilya checks once on the SE before S3. Mitigation: the S1 fallback layout already keeps Post closer, and docking only applies when detection succeeds.
- Shared docking code: the reply composer depends on the same script, and `v3/composer_anchor_exit` (Step 1, unapproved) also plans to touch it. Early validation: run all `reply_viewport*`, `reply_continuation` and `reply_compose_focus` browser tests after S2. Mitigation: add a second hook without changing reply behavior.
- Shared composer partial: reordering tools changes the paned new-thread dialog too. Validation: `compose_disclosure`, `compose_layout_stability`, `composers` tests and a desktop paned-dialog check in S3.
## Stage 1
**Estimate: 0.08**
- Goal: the dedicated form has the new order and a phone-friendly flow, with or without JavaScript.
- Dependencies: none.
- Expected changes: in `composer.php`, render the tools row (tags, event) after `.compose-form-actions` for the full composer only. In `compose.css`, under 640px on `.compose-page`, drop the reserved hidden image-picker footprint that sits between the body and Post (keep it on wider screens, where it prevents layout shift), and shorten the default body height toward the compact form's.
- Verification approach: render the partial in PHP for full and compact modes and check element order. Headless WebKit at 375 x 667 and 375 x 300 in all 13 themes: Post's distance from the top of the body shrinks, with no horizontal overflow. `compose_layout_stability` and `compose_disclosure` pass.
- Estimate details: driver: shared partial and the hidden-footprint rule; uncertainty: low.
- Risks or open questions:
- Impact: removing the footprint could reintroduce the image-eligibility shift the rule prevents.
- Early warning / validation: `compose_layout_stability` on phone widths.
- Mitigation: keep the footprint but place it after Post instead of removing it, if the test fails.
- Canonical components/API contracts touched: `templates/partials/composer.php`, `public/assets/compose.css`; form field names and submit contract unchanged.
## Stage 2
**Estimate: 0.15**
- Goal: when the keyboard is up on `/compose/thread`, the form docks above it exactly as the phone reply panel does.
- Dependencies: S1.
- Expected changes: in `reply_viewport.js`, accept a neutral `data-phone-composer` form hook alongside `data-phone-reply`, with the panel found the same way (`[data-reply-panel]`). Reply-only parts (`forum:reply-sent` reading mode) stay reply-only. `compose_thread.php` marks its card as the panel and its form with the hook; the paned dialog does not opt in. In `compose.css`, while docked, the body flexes, collapsed tools are hidden (an open tool stays and the existing fit checks fall back to page flow if it doesn't fit), and the title follows Q2. A `?debug-viewport=1` mode shows the measured heights for the device check.
- Verification approach: fake `visualViewport` harness (as in `reply_viewport.mjs`) at 375 x 300 with the compose form: docked, editor at least 80px, editor and actions don't overlap, actions inside the visible area, caret and selection kept, an 80-line draft, error feedback and the open "..." menu all fit. All existing reply viewport tests pass unchanged.
- Estimate details: driver: generalizing shared script hooks without reply regressions; uncertainty: medium (iOS Firefox detection).
- Risks or open questions:
- Impact: a reply regression would break the most-used composer.
- Early warning / validation: the reply tests listed in Key Risks, run before the compose tests.
- Mitigation: additive hook; no changes to reply thresholds or timing.
- Canonical components/API contracts touched: `public/assets/reply_viewport.js` (docking contract: `[data-reply-panel]`, `--reply-panel-*` variables, `.reply-panel-active`), `templates/pages/compose_thread.php`, `public/assets/compose.css`.
## Stage 3
**Estimate: 0.12**
- Goal: lock the behavior in with automated checks across themes and composers.
- Dependencies: S2; Ilya's device height check from S2.
- Expected changes: add `tests/browser/compose_thread_viewport.mjs`: 13 themes at 375 x 667 and 375 x 300 (fake keyboard), WebKit and Chromium. It checks order (tags after Post), docking, no overlap, no horizontal overflow, short and long drafts, error retry, and no-JavaScript flow. Run the existing composer, disclosure, layout stability, reply viewport, tab bar and image tests.
- Verification approach: all new and listed existing tests pass, with results recorded in the Step 4 summary.
- Estimate details: driver: 13-theme matrix and fake-viewport setup; uncertainty: low (the harness already exists for replies).
- Risks or open questions:
- Impact: icon themes (Word 97, Chicago, Midnight) have fixed status bars that eat vertical space.
- Early warning / validation: the theme matrix at 375 x 300.
- Mitigation: the reply panel's reading mode already measures a fixed `.site-status-bar`; apply the same measurement to the docked height if these themes fail.
- Canonical components/API contracts touched: browser test harness in `tests/browser/`; no product contracts.
## Stage 4
**Estimate: 0.07**
- Goal: replay the reported failure on the real phone and close out.
- Dependencies: S3; Ilya with the iPhone SE.
- Expected changes: Ilya opens New post in Firefox and Safari, types a short and a long draft with the keyboard open, and posts once (a test thread he can retract). Record device, iOS and browser versions and the result in the Step 4 summary. Add a `docs/concepts/composer.md` entry for the shared composer and its phone docking, and record the compose page's phone verification in `docs/plans/ui_copy_audit_checklist.md`. Add the closeout checkpoint to the effort ledger.
- Verification approach: the device result itself; headless results don't count here. A failure goes back to S2 with the observed heights.
- Estimate details: driver: waiting on the device check; uncertainty: medium (unknown device result).
- Risks or open questions:
- Impact: a device-only failure after automated tests pass.
- Early warning / validation: the S2 height check catches detection problems early.
- Mitigation: the S1 fallback still improves the page; record the result and the follow-up honestly.
- Canonical components/API contracts touched: `docs/concepts/` (new composer entry), effort ledger.
## Concepts touched
- Composer (new entry `docs/concepts/composer.md`, S4): changed (order, phone docking for the dedicated page).
- Phone reply panel docking (described within the composer entry): extended with a second hook, reply behavior unchanged.
- App shell / bottom tab bar: used unchanged.
## Open questions for Ilya (defaults in brackets)
- Q1: while the keyboard is up, can the collapsed tags/event row hide until the keyboard closes? [yes]
- Q2: should the title stay on screen while docked, or hide when it's empty and the body has focus, to give the SE about 50px more writing space? [stay, per the clarification]
- Q3: should the paned new-thread dialog get the new order only, without docking, since it's a wide-screen layout? [yes]
## Enhancement Requests
- **Request:** Word 97 / Midnight phone overflow and Sticker / Arena Post padding at 375px. **Disposition and reason:** separate work; different cause. **Approval status:** pending. **Follow-up:** separate cycle on request.
## Effort snapshot
2026-10-31T06:15Z · Step 3 handoff · cycle `v3/mobile_compose_keyboard` · scope v1 unchanged · baseline `63f0849691902e665506ad4f23f374353a32ae78` · R1 (three hashes verified) · P2 · Claude single exposed rater (also inspected `reply_viewport.js`, its tests and `compose.css` for this plan) · **0.40 projected total, reaffirmed**. Stage work S1 0.08, S2 0.15, S3 0.12, S4 0.07; independent stage judgments, not summed into the cycle value. Reason unchanged: reuse of the existing docking script plus a reorder; the theme matrix and the device check keep it above the smallest references.
## Approval
Step 2 approved. Answer Q1–Q3 if the defaults are wrong, then reply "Approved Step 3" and I'll start on a feature branch.
*— Claude · 2026-10-31 UTC · Model: Claude Opus 5.5*
#meta #bug #railspike