Skip to content

Show margin discussions as Google-Docs-style cards - #316

Open
ThePumpingLemma wants to merge 6 commits into
block:mainfrom
ThePumpingLemma:dgrochowski/comment-cards
Open

ThePumpingLemma wants to merge 6 commits into
block:mainfrom
ThePumpingLemma:dgrochowski/comment-cards

Conversation

@ThePumpingLemma

@ThePumpingLemma ThePumpingLemma commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

What changed and why

Adds a Cards mode to the plan page's right-hand discussion margin, like the comment column in Google Docs. Today the margin shows one avatar marker per highlighted discussion. In Cards mode, each open discussion appears as a card beside its highlight. A card shows:

  • the first commenter's avatar, name, relative time and comment, clamped to four lines with "more";
  • the reply count and the latest participant ("2 replies, latest from Noura Haddad");
  • the status (Open, or Resolved when Show resolved is on).

Layout. Cards line up with their highlights. When highlights are close, the later card moves down, using the existing stacking pass (STACK_GAP, position()). The active card lines up with its text when it fits on screen. Taller windows lift to stay below the pinned navigation. Earlier cards make room above the window, or below it when there is no room above.

Interaction

  • Clicking a card, or its highlight, makes the card active. The existing thread popover from #plan-threads opens over the card, at the card's width, so the card expands in place into the full thread, with reply and resolve. There is still one thread UI and one source of truth. The new helper placeOnMarginCard in coplan/comment_window.js handles the placement, and text_selection and comment_nav both call it, so clicks, j/k and ?thread= all land on the card.
  • The active highlight is emphasised with the existing active-highlight treatment. Hover links a card and its highlight both ways; that already worked for markers.
  • The expanded card grows as replies arrive, and the cards below move down. If the expanded card runs past the bottom of the window, it moves up to keep its reply box and Resolve on screen, below the pinned navigation. Opening, drafting, and replying keep the document scroll position fixed. It is checked again whenever the open window grows, for example while drafting or after a reply.

Switch. A small Markers / Cards switch sits in the masthead, beside the plan toolbar. It is remembered per user in localStorage (coplan:comment-margin:<user id>). It shows only while the margin has discussions to show, and is hidden below 1200px, where there is no margin.

Default: Markers.

  • docs/DESIGN.md makes reading the primary activity and asks for compact, muted badges for unresolved discussions. Markers keep the margin quiet, and Cards is an opt-in for review sessions.
  • Cards need about 1,650px of window (240px or more beside the 1,104px document card). With Cards as the default, most laptop windows would show markers anyway.
  • Existing readers see no change.

Narrow windows. When the cards don't fit beside the document card, the margin falls back to markers automatically. The choice is kept, and the switch's Cards tooltip explains that markers are showing.

Scope

  • Resolved discussions stay hidden unless Show resolved (s) is on.
  • Document-level discussions stay in the Comments section.
  • Broadcasts and the existing MutationObserver keep cards current: new replies and new threads appear without a reload.

Accessibility. The margin is an aside labelled "Discussions". Cards are <button>s in document order, with aria-expanded and a visible blue focus ring. In Cards mode the card's text is its accessible name; markers keep their existing aria-label. There is no orange: the status is a muted label.

Collapsed sections (#315). This PR is rebased on main, which includes #315 and #318. The two features now combine:

Evidence

Light and dark screenshots are attached below. Additional maintenance evidence is in this comment.

  • /tmp/coplan-shots/comment-cards/cards-light.png, cards-dark.png: the cards column, including the stacked pair and the truncated long comment
  • /tmp/coplan-shots/comment-cards/active-card-light.png, active-card-dark.png: an active card expanded into the full thread, with the cards above moved up
  • /tmp/coplan-shots/comment-cards/narrow-markers-light.png, narrow-markers-dark.png: a 1400px window falling back to markers
  • /tmp/coplan-shots/comment-cards/closed-section-badge-light.png, closed-section-badge-dark.png: Cards mode on the collapsed-sections demo, with closed sections showing badges and no cards

How to try it

  1. bin/setup --skip-server, then COPLAN_DEMO_BASE_URL=http://localhost:3104 bin/rails db:seed and bin/rails server -p 3104.
  2. Sign in as hampton@example.com at http://localhost:3104/sign_in.
  3. Open http://localhost:3104/hampton/demo-content/discussions/comment-cards-margin-discussions (Demo library, Discussions, "Comment cards: margin discussions") in a window at least about 1,650px wide.
  4. Choose Cards in the switch beside Edit. You should see five cards beside their highlights. The "cache warmup" / "retry budget" pair shares a line, so the second card sits below the first. Noura's long comment ends with "more".
  5. Click a card or a highlight: it expands into the full thread, and you can reply or resolve there. Press j/k to move between cards. Hover a card to light up its highlight, and the other way round.
  6. Press s to show the dimmed resolved card. Reload: Cards is remembered. Narrow the window below about 1,650px: the margin falls back to markers.

Testing

  • spec/system/comment_cards_spec.rb (14 examples) covers:

    • cards are aligned, non-overlapping and stacked, with content, truncation and the resolved state;
    • clicking a card opens the thread over it, and replying updates the card;
    • hover links the card and highlight both ways, and clicking a highlight activates its card;
    • j/k activate cards;
    • ?thread= activates its card;
    • the switch changes modes and persists across reloads;
    • a narrow window falls back to markers and returns to cards;
    • comment and thread broadcasts update cards;
    • a deck-slide passage scrolled out of its slide hides its card in Cards mode;
    • near the end of a short page, an expanded card keeps its reply box and Resolve inside the viewport, while a draft grows it and after a reply;
    • a closed section shows its badge and no card, and the badge opens the section with its card active.
  • spec/lib/development_seed_spec.rb covers the new comment-cards example's initial state and re-seeding.

  • After the rebase onto b40c2c3 (including Reach discussions inside collapsed details sections #315 and Render LaTeX math with KaTeX #318), on the final commit:

    • comment_cards_spec.rb and collapsed_section_comments_spec.rb: 22 examples, 1 failure. The failure was a Chrome "Timed out receiving message from renderer" on the ?thread= example, which then passed in 3 of 3 runs on its own.
    • full bundle exec rspec: 3491 examples, 1 failure in 18m05s. The failure is spec/system/agent_connections_spec.rb:16 ("failed to close window in 20 seconds"). It is a known local Chrome flake: an unrelated tab Chrome opens on its own blocks Capybara's reset. It is unrelated to this change.
  • While iterating, the same flake landed once per browser session on whichever comment_cards_spec.rb example happened to be running. Those examples pass when run again.

  • Manual check in Chrome at 1728px and 1400px, in light and dark mode.

  • Added or updated real JavaScript-enabled browser specs in spec/system/ for changed UI behavior, covering the user interaction and visible result; or explained why no browser UI is involved.

  • Added model, service, or request specs where needed for domain behavior, authorization, or API cases. (The seed spec covers the demo example; there is no new server behaviour.)

  • Ran bundle exec rspec, or explained why it could not run and listed the specs that did run.

  • Tested affected UI behavior in both light and dark mode, or explained why mode testing does not apply.

  • Attached a video or images demonstrating the change, if this PR adds or changes a feature or fixes a UI bug. (Screenshots to be attached.)

active-card-dark active-card-light cards-dark cards-light narrow-markers-dark narrow-markers-light

Maintenance verification: 150 examples passed across comment_cards_spec.rb, collapsed_section_comments_spec.rb, and spec/requests/plans_spec.rb. The switch reserves its dimensions until layout is known; browser regressions cover collapsed-only discussions and windows where even markers cannot fit.

@joahg joahg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 Nice direction. Cards over the existing popover keeps one thread UI, the cards stay in document order, and Markers as the default matches DESIGN.md. I'm leaving comments rather than a verdict since this is still a draft. Before it's marked ready:

  1. Fix the [hidden] CSS override (inline). It's a real bug for deck slides.
  2. Add a bottom fallback for card windows that can't scroll into view (inline).
  3. Rebase onto #315 using the plan in the description: toggle for <details> and beforetoggle for popovers, keep #315's hidden map alongside your activeId carry-over in refresh(), and add a spec that a closed section shows a badge and no card. Your markActive in refresh() also fixes an is-active gap #315 has on its own.
  4. Specs for hidden-slide cards and a card near the bottom of a short page. Also attach the light and dark screenshots.

Add a Cards mode to the plan page's discussion margin. Each open
discussion becomes a card aligned with its highlight, showing the first
comment, the reply count with the latest participant, and the status.
Close cards stack downward; the active card lines up with its text and
the existing thread popover expands over it, so replies and resolve
still use one thread UI. Hover links cards and highlights, and j/k and
?thread= activate the matching card.

A Markers / Cards switch in the masthead is remembered per user in
localStorage. Markers stay the default, and Cards fall back to markers
when the margin is too narrow.

Adds the comment-cards demo-library example with seed coverage and a
browser spec for cards.
- Restate [hidden] for cards-mode cards, and cover a deck-slide passage
  scrolled out of its slide in Cards mode.
- Re-check card windows whenever they open or grow: scroll the page to
  show the reply box and Resolve, and when the page cannot scroll far
  enough, lift the window just enough, below the pinned navigation.
- Clone the first commenter's avatar instead of re-parsing its HTML, and
  key the card signature on its class, initials, and photo address.
- Move a window opened from a section badge onto its card once the
  section opens, and cover closed sections in Cards mode.
@ThePumpingLemma
ThePumpingLemma force-pushed the dgrochowski/comment-cards branch from c01eab0 to 3002a40 Compare November 9, 2026 19:36
@ThePumpingLemma

Copy link
Copy Markdown
Contributor Author

@joahg I rebased this PR onto main (b40c2c3, which includes #315 and #318) and worked through your review. The head is now two commits:

  • 0982358: the rebased feature;
  • 3002a40: the review fixes.
  1. The [hidden] override: done. The cards-mode [hidden] rule is restated, and a new Cards-mode deck-slide spec checks that a card whose passage scrolls out of its slide is hidden and comes back aligned. Note: settings.css already has a global [hidden] { display: none !important; }, so those cards were in fact already hidden. The spec passes either way, and the explicit rule keeps the cards layout from depending on the global one.
  2. Bottom fallback for card windows: done. A card window stays on its card while scrolling can show it. Otherwise it moves up to keep the reply box and Resolve on screen, never above the pinned navigation. It is re-checked whenever the open window grows, through the ResizeObserver on the open window. The covering spec uses a card near the end of a short page and checks the window on open, while a draft grows it, and after a reply. It fails with the fallback removed.
  3. Rebase onto Reach discussions inside collapsed details sections #315: done, following your plan:
  4. Specs for hidden-slide cards and a card near the bottom of a short page: done; both are in spec/system/comment_cards_spec.rb. The avatar nit is done too: the avatar node is cloned, and its class, initials and src form the signature.

Tests: the full bundle exec rspec gave 3491 examples, 1 failure. The failure is the known local Chrome window-close flake in agent_connections_spec.rb:16.

Screenshots: fresh light and dark screenshots are saved locally under /tmp/coplan-shots/comment-cards/: cards, an active card, narrow-window markers, and a closed section's badge in Cards mode. They still need to be attached to this PR by hand.

@ThePumpingLemma
ThePumpingLemma marked this pull request as ready for review November 9, 2026 19:39
@ThePumpingLemma

Copy link
Copy Markdown
Contributor Author

Updated screenshots:

active-card-dark active-card-light cards-dark cards-light closed-section-badge-dark closed-section-badge-light narrow-markers-dark narrow-markers-light

@HamptonMakes

Copy link
Copy Markdown
Collaborator

🤖 @codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-09T20:30:56.900432Z 9a30c33 Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

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

Copy link
Copy Markdown

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: 3002a40166

ℹ️ 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".

Signed-off-by: Hampton Lintorn-Catlin <hampton@squareup.com>
@HamptonMakes

Copy link
Copy Markdown
Collaborator

🤖 @codex review

@HamptonMakes

Copy link
Copy Markdown
Collaborator

🤖 Fresh browser evidence for 9a30c338 (isolated preview and shared demo library; synthetic accounts).

Light and dark cards, including a lifted active discussion:

Light Dark
Cards in light mode Cards in dark mode
Active card in light mode Active card in dark mode
Closed section badges in light mode Closed section badges in dark mode
Narrow-window marker fallback in light mode Narrow-window marker fallback in dark mode

The browser check confirmed unchanged document scroll while drafting (0 → 0; previously 442 → 503), R focuses the reply field, a 390px viewport uses the bottom sheet, and a scrolled-out deck passage hides its card. No page JavaScript errors occurred. The focused test run passed 148 examples. CI and the second Codex review are still running; this is evidence, not an approval or merge recommendation yet.

Signed-off-by: Hampton Lintorn-Catlin <hampton@squareup.com>

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

Copy link
Copy Markdown

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: 9a30c33895

ℹ️ 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".

Signed-off-by: Hampton Lintorn-Catlin <hampton@squareup.com>
@HamptonMakes

HamptonMakes commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

🤖 Engineering cleanup complete for Hampton's hands-on review. No approval or merge was requested or performed.

  • Current head: 8bb46256. Current-head CI: 15/15 checks passed. GitHub: MERGEABLE / BLOCKED, awaiting required approval.
  • Pushed 9a30c338 (keep document scroll fixed; reserve the entire lifted discussion window), 1da22a5e (strengthen the initial-state regression), a58360a3 (reserve switch dimensions; hide unavailable modes), and 8bb46256 (wait for the existing card-layout assertion to settle).
  • The a58360a3 run exposed a measurement race in the card-opening spec: it read the card before the ResizeObserver completed placement. The fix retains the same one-pixel alignment requirement and retries until layout settles. 14 card examples pass with the failing CI seed 15564; Ruby lint is clean. The earlier combined focused run passed 150 examples, and demo-seed coverage passed. No additional suite or review gate was added.
  • Two Codex rounds completed (3002a401, 9a30c338); all five actionable findings were fixed and threads resolved. The subsequent switch correction and test stabilization are bounded, with browser regressions and unchanged product scope, so another review round was not requested.
  • Verified the author's rebase and replies. The original hidden-card allegation was already prevented by global [hidden] !important; the explicit rule is hardening. Avatar cloning, hidden-slide behavior, and collapsed-section active state are intact.
  • Light/dark evidence. Exercised reply growth, keyboard focus, collapsed badges, hidden slides, marker fallback, 390px bottom sheet, and draft preservation through resizing. Opening, drafting, replying, and dismissal keep document scroll fixed.

Product recommendation: keep Cards as an opt-in wide-screen review mode and Markers as the reading default. Cards preserve the document's width and use blue interaction states without treating unresolved threads as unread. Dense conversations can spread far from their passages; when a tall active discussion fills the available column, earlier cards move below it to remain accessible. Hampton should judge that density and movement in the tryout.

TRY THIS: http://localhost:3316/hampton/demo-content/discussions/comment-cards-margin-discussions — login hampton@example.com, no password. Around 1760px wide, choose Cards, open the retry-budget discussion, and grow a reply. The page should stay still and controls remain reachable. Narrow to 1400px for markers, or open the collapsed-sections demo from the shared library. The isolated preview remains running for feedback. Final acceptance and approval/merge remain Hampton's decision.

Signed-off-by: Hampton Lintorn-Catlin <hampton@squareup.com>

@joahg joahg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 Re-review of 8bb4625. All four points from my earlier review are addressed. (1) The cards-mode [hidden] rule is restated after the card rule, with a deck-slide spec. (2) Card windows now get a bottom bound via marginCardWindowTop and are re-placed through the ResizeObserver on the open window, with a short-page spec covering open, draft growth and reply; the later change to stop scrolling the document is a good tightening. (3) The #315 combination is done: toggle for details, beforetoggle for popovers, the hidden-section map kept next to the activeId carry-over, and a closed-section spec showing a badge and no card. (4) The avatar is now cloned with cloneNode instead of re-parsed. Other checks: card text (author, time, body) is set via textContent, the only innerHTML is the constant icon SVG, and the thread id used in selectors comes from server-rendered ids, so I see no XSS path. There are no new endpoints or server behavior (the diff is JS, CSS, a view partial, a demo seed and specs), so there is no authorization surface. The localStorage key is per user and falls back to markers on any error. CI is 15/15 green. None of the author's six new comments asks me a question; they report what was fixed. I did not run the specs or the UI locally and only skimmed the CSS and the seed content.

@HamptonMakes HamptonMakes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This one is going to be a no for me at least as-it-is... this takes up a huge amount of upper real-estate to have the card/marker swapper UI. And that appears before it's actually useful. And a toggle is just the wrong metaphor.

This also massively extends the number of variants for display vs modes, etc.

And, the 'too many comments stacked chaos' is something on Google Docs that I haaate. Comments end up stacked up and confusingly sort of jammed along the side.

If we wanted this to be something more subtle like an auto-opt in when large and also more show/hide expand than having the traffic jam of comments, I'm still up for coming up with something there...

But as-is this isn't something I want in coplan with how it works right now.

image

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.

3 participants