Public landing page and live plan content updates (CIRCLE-49, CIRCLE-50)#113
Conversation
…CLE-50)
CIRCLE-49 — Public welcome / landing page
- New GET /welcome route always shows a real landing page explaining
what CoPlan is, with hero, three-step workflow, and an agents section.
- "/" now routes to WelcomeController:
- signed-in users with plans → redirected to /plans
- anonymous visitors and signed-in users with no plans → see the
landing page instead of an unexplained list of plans
- ?force=1 escape hatch always renders the landing page
- Added CoPlan.configuration.landing_page_partial so a host app
(e.g. coplan-square) can override the default copy with its own
partial. Default engine partial ships with generic copy.
- Removed dead, unrouted DashboardController + view.
- Updated session specs because "/" is intentionally public now;
auth-gated redirect assertions point at /plans.
CIRCLE-50 — Live plan content updates with dirty-draft protection
- Root cause: every plan mutation path only broadcast header
updates over Turbo Streams, so the document body never updated
in open tabs.
- Extracted the rendered plan markdown into _content_body.html.erb
wrapped in #plan-content-body so it can be targeted by Turbo.
- Added a custom Turbo Stream action coplan-replace-if-clean that:
- replaces the body live when the local tab has no dirty draft
- otherwise leaves the body alone and shows a sticky warning
banner with a reload button, so an unsaved comment / reply
is not clobbered
- Wired all mutation paths to broadcast the new body partial:
- Plans::ReplaceContent
- Plans::CommitSession
- Api::V1::OperationsController#broadcast_plan_update
- PlansController#toggle_checkbox
- Dirty-state detection is intentionally conservative: any
textarea or [contenteditable=true] with trimmed non-empty
text counts as dirty, which covers new-comment and reply forms
without per-form wiring.
Testing
- bundle exec rspec → 845 examples, 0 failures
- Manually verified locally: API-driven edit propagates into an
open tab; with text typed into a reply, body stays stale and
the warning banner appears without losing the typed text.
Amp-Thread-ID: https://ampcode.com/threads/T-019e22e9-1c86-71bc-a076-f5e6d06b03d0
Co-authored-by: Amp <amp@ampcode.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3fa37d3dab
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (hasDirtyDrafts()) { | ||
| showStaleBanner(target, incomingRevision) | ||
| } else { | ||
| target.replaceChildren(fragment) |
There was a problem hiding this comment.
Re-run anchor highlighting after live body replacement
Replacing #plan-content-body with target.replaceChildren(fragment) removes all existing <mark class="anchor-highlight..."> nodes, but this path never triggers text_selection_controller#highlightAnchors again. That controller only re-highlights on connect and when #plan-threads mutates, so after a clean live update the inline comment highlights (and dependent margin-dot/navigation state) disappear until a reload or thread mutation, breaking the core review UX for open plan tabs.
Useful? React with 👍 / 👎.
…3) (#114) The "Built for any AI agent" section on the landing page is the one piece hosts most often want to customize: generic CoPlan tells users to read /agent-instructions, but a deployment like coplan-square wants to tell its users to run `sq agents skills add coplan` instead. Rather than make hosts fork the entire landing page just to change that paragraph (which is what `landing_page_partial` would force them to do), expose a single config knob for just this section. - Extract the agents section into _default_agents.html.erb - Add CoPlan.configuration.landing_agents_partial, defaulting to it - _default_landing.html.erb renders via the configured partial - Add request specs for the default and the override path, asserting the rest of the landing page stays unchanged when only this section is swapped Behavior is unchanged by default — the engine renders the same agents paragraph it did before. Host-side change to actually use this hook will ship in coplan-square in a follow-up. Stacked on top of #113 (which introduced the landing page). Amp-Thread-ID: https://ampcode.com/threads/T-019e22e9-1c86-71bc-a076-f5e6d06b03d0 Co-authored-by: Amp <amp@ampcode.com>
Why
Two product-facing problems we kept hitting:
What
COPLAN-2 — Public welcome / landing page
GET /welcomeroute that always shows a real landing page (hero, three-step workflow, agents section)./now routes toWelcomeController:/plans?force=1escape hatch always renders the landing pageCoPlan.configuration.landing_page_partialso a host app (e.g.coplan-square) can override the copy with its own partial. Default engine partial ships generic copy.DashboardController+ view that was no longer routed./is intentionally public now; auth-gated redirect assertions point at/plans.COPLAN-1 — Live plan content updates with dirty-draft protection
_content_body.html.erb, wrapped in#plan-content-bodyso Turbo can target it.coplan-replace-if-clean:Plans::ReplaceContentPlans::CommitSessionApi::V1::OperationsController#broadcast_plan_updatePlansController#toggle_checkbox<textarea>or[contenteditable="true"]with trimmed non-empty text counts as dirty, which covers new-comment and reply forms without per-form wiring.Testing
bundle exec rspec→ 845 examples, 0 failuresReferences
🤖 Generated with Amp