Skip to content

Public landing page and live plan content updates (CIRCLE-49, CIRCLE-50)#113

Merged
HamptonMakes merged 2 commits into
mainfrom
hampton/circle-49-50/landing-and-live-updates
May 21, 2026
Merged

Public landing page and live plan content updates (CIRCLE-49, CIRCLE-50)#113
HamptonMakes merged 2 commits into
mainfrom
hampton/circle-49-50/landing-and-live-updates

Conversation

@HamptonMakes
Copy link
Copy Markdown
Collaborator

@HamptonMakes HamptonMakes commented May 18, 2026

Why

Two product-facing problems we kept hitting:

  1. People who land on the CoPlan root URL see a wall of unfamiliar plans with zero context about what the site is for. We need a real landing page (COPLAN-2).
  2. Plan content does not live-update in open tabs when an agent (or another user) edits a plan elsewhere. I had a tab open, an agent edited the doc, and my tab silently stayed stale. We also need to be careful not to clobber an in-progress comment draft when we do start live-updating (COPLAN-1).

What

COPLAN-2 — Public welcome / landing page

  • New GET /welcome route that always shows a real landing page (hero, three-step workflow, 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
    • ?force=1 escape hatch always renders the landing page
  • New CoPlan.configuration.landing_page_partial so a host app (e.g. coplan-square) can override the copy with its own partial. Default engine partial ships generic copy.
  • Removed dead DashboardController + view that was no longer routed.
  • Session specs updated because / is intentionally public now; auth-gated redirect assertions point at /plans.

COPLAN-1 — 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 Turbo can target it.
  • Custom Turbo Stream action coplan-replace-if-clean:
    • 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 does not get 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 rspec845 examples, 0 failures
  • Manually verified locally:
    • API-driven edit propagates into an open tab without reload
    • With text typed into a reply textarea, the body stays stale and the warning banner appears; typed text is preserved

References


🤖 Generated with Amp

…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>
@HamptonMakes
Copy link
Copy Markdown
Collaborator Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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>
@HamptonMakes HamptonMakes merged commit 19f514b into main May 21, 2026
5 checks passed
@HamptonMakes HamptonMakes deleted the hampton/circle-49-50/landing-and-live-updates branch May 21, 2026 21:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant