Skip to content

feat(composer): edit shared posts and share-to-squad in the new composer - #6422

Open
tsahimatsliah wants to merge 37 commits into
mainfrom
claude/edit-post-new-composer
Open

feat(composer): edit shared posts and share-to-squad in the new composer#6422
tsahimatsliah wants to merge 37 commits into
mainfrom
claude/edit-post-new-composer

Conversation

@tsahimatsliah

@tsahimatsliahtsahimatsliah commented Aug 4, 2026

Copy link
Copy Markdown
Member

Changes

Two remaining old-composer surfaces move to SmartComposerModal: editing a shared post and sharing a post to a squad.

Editing a share in the composer

Editing a shared link still opened the old /posts/[id]/edit page, with the old top-toolbar editor, while every other composer surface had moved to SmartComposerModal.

Two reasons, not one:

  1. PostOptionButton only routed Freeform and Welcome posts to the modal; everything else fell through to router.push('<permalink>/edit').
  2. Even if it had routed there, the modal forced kind = 'text' whenever editPost was set — it had no way to represent a share.

What changed:

  • Kind is derived from editPost.type instead of hard-coded to text.
  • Commentary is prefilled the way the old page derived it: a share posted without commentary carries the shared post's own title, which is not the author's writing, so it is offered back as an empty field rather than as their text.
  • The preview is seeded from editPost.sharedPost instead of being refetched — there is no URL being typed for it to resolve.
  • The URL is locked (LinkForm isUrlLocked): only the commentary is editable, which is all updateSquadPost accepts. The URL field and the preview's remove button are dropped, and focus starts in the commentary.
  • submitLink gained the edit branch it never had — only submitText handled editPostId. It routes through onUpdateSharePost, including the moderation path, which completes without a toast of its own (the plain update path already toasts from usePostToSquad, so adding one there would double it).
  • Submit validation no longer demands a resolved URL/preview pair while editing, which would otherwise leave the button permanently disabled.
  • Dirty-tracking compares the commentary rather than the text fields, so closing an untouched share edit no longer prompts "Discard changes?".

Share to squad through the composer

Clicking a squad avatar in ShareBar, DiscussionShareRow, or the Share modal opened CreateSharedPostModal — the old editor. All three now open SmartComposerModal seeded for the share:

  • The post rides in as the link preview (preview={post}, initialUrl={post.permalink}), so nothing is refetched and the composer's existing internal-share path applies: preview.id → addPostToSquad, moderation when the squad requires it, and multi-squad sharing via sharedPostId — a capability the old modal never had.
  • The clicked squad is preselected via initialSquadId, but the audience stays editable.
  • A new onPosted callback on SmartComposerModal carries the ShareToSquad analytics event the old modal fired from onSharedSuccessfully.
  • CreateSharedPostModal is deleted along with its spec and its LazyModal registration; SocialShare renders the composer inline for the same dep-cycle reason it rendered the old modal inline.

What deliberately did not move

  • Moderation items keep /posts/[id]/edit. That page edits a pending submission (useSourcePostModerationById), which is a different entity from a post — the composer cannot represent it.
  • LiveRoomChatPanel stays as it is. It is a chat input, not a post composer: one line, send-on-enter, in a sidebar. A full-screen drawer and a bottom action bar would make it worse.
  • ControlledMarkdownInput (profile bio) is a plain markdown field, not a composer.

Dead code removed

CreateSharedPostModal, CreateLiveRoomForm, and CreatePoll were each left referenced by nothing (or only their own specs), so they are deleted rather than carried forward.

Events

No new tracking events — StartShareToSquad/ShareToSquad keep firing from the same triggers.

Experiment

No new experiments.

Preview domain

https://claude-edit-post-new-composer.preview.app.daily.dev

tsahimatsliahand others added 19 commits August 3, 2026 14:26
The reported bug: on mobile a long comment overflowed with nowhere to
scroll, so the text ended up hidden behind the virtual keyboard.
Root cause was two-fold. `CommentMarkdownInput` drove `RichTextInput`
with the default top toolbar, which renders no scroll container and no
max-height, so the editor just grew. `CommentModal` then force-sized the
form to the visual viewport by writing `style.height` during render —
producing the literal "autopx" whenever the maths went negative.
The composer is now the same inline box as the watercooler create-post
composer, minus title, cover, post type and audience: bottom toolbar,
one action bar, and a height capped against `visualViewport` (the part
that survives the keyboard) with the body scrolling inside it. Same
component on every viewport, so `CommentModal` and the mobile/desktop
fork are gone. The mobile floating bar now opens the in-page composer
through a window event instead of mounting its own copy inside the
fixed footer.
Several fixes landed in shared primitives and so reach every composer,
including create post:
- RichTextInput: rich and markdown modes share one tree, so switching no
longer remounts the avatar (which refetched the image and blinked) or
the action bar. Heights now match between modes, and the scroll offset
and caret are preserved across the toggle.
- RichTextToolbar: the overflow budget ignored dividers and row gaps, and
the overflow button lived inside the `overflow-hidden` group it exists
to protect, so it got sliced. Also swapped to the three-dots icon and
kept it beside the formatting items.
- Drawer: closing on any click whose target was outside the panel also
caught portaled dropdowns, whose synthetic clicks bubble up the React
tree. Picking a post type, audience, poll duration or schedule tore the
drawer down instead of running the action. Now only a backdrop hit
closes it.
- Switch: the label could not shrink, so long copy ran off the right edge
on mobile.
- ProseMirror no longer adds a second min-height inside the editor's own
padding.
- AudienceChip and the poll option input could not shrink either.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Swapping the rich editor for the textarea briefly shrinks the scroll
container, so the browser clamps its offset to 0. Restoring from a later
animation frame was too late in one direction: markdown to rich held, but
rich to markdown always landed back at the top. Restore right after the
textarea gets its real height, before paint, guarded by a flag so normal
typing does not fight the user's own scrolling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Engineering review pass over the composer. No behaviour or visual change.
- Stories move under stories/components/comments/, next to the existing
CommentContainer story, and are retitled Components/Comments/*. The
storybook AGENTS.md groups stories by atomic design, and a top-level
`comments/` group also sorted outside preview.tsx's storySort order.
Added the `autodocs` tag the same guide asks for.
- The header label goes through Typography rather than a raw span with
typo-* classes, per the components guide.
- The composer form carries an aria-label, so it exposes role="form" to
assistive tech. The spec queries that instead of a data-testid, which
the components guide reserves for when no accessible query exists.
- Drop three props left dead by removing CommentModal: `style`,
`showUserAvatar` and `CommentClassName.markdownContainer`. Nothing
passed them any more, and the comment composer always wants its avatar.
- Flatten CommentInput's `className.input` nesting to `CommentClassName`.
The wrapper object only existed to sit beside the `modal` key that went
with the deleted modal branch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The changed-file guard reports all strict errors in a touched file, not
just newly introduced ones, so editing these files made their existing
violations block CI. Fixed rather than suppressed; no behaviour change.
- isSourcePublicSquad accepts an optional Source, which is what its body
already assumes via optional chaining.
- PostEngagements: useRef<NewCommentRef>(null) for a LegacyRef-compatible
ref, and `false | value` unions replaced with undefined branches.
- Drawer: container ref nullable, forwardRef render fn typed with
ForwardedRef. The debounced close now forwards the event that triggered
it instead of dropping it and calling onClose with undefined.
- PostComments.modalParentSelector may return null, matching
getCompanionWrapper; the call site already null-coalesced it.
- CompanionDiscussion returns ReactElement | null, which it always did.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mobile round of feedback on the composer surfaces:
- On mobile the comment composer opens as a full-screen Drawer, the same
way creating a post does — an inline box wedged into the page is too
small to write in. It autofocuses so the keyboard opens ready to type.
A new `fills` mode makes the composer fill the drawer instead of
capping against the viewport, dropping the inline card treatment.
- Full-screen drawers size themselves to the visual viewport (height and
offsetTop), so bottom action bars sit above the virtual keyboard
instead of underneath it. useVisualViewport now reports offsetTop and
listens to viewport scroll for the iOS keyboard-pan case.
- The create-post kind picker moves to its own row above the action bar
on mobile (`stackToolbarLeading`). The bar's overflow menu only
relocates formatting items, so the picker plus the icon actions
overflowed the clipped group and rendered sliced.
- Expand/collapse is hidden on mobile — the composer is already
full-screen there, so the control was a no-op.
- Drawer wrapper overrides use `!px-0 !pt-0` (matching NavDrawer): plain
`p-0` lost to the drawer's default padding, so the composer paid
padding twice, and killing padding-bottom outright would also have
dropped the safe-area inset that `drawer-padding` provides.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Animated/transformed ancestors (the animate-composer-in wrapper) create
a containing block for position: fixed, pinning the drawer mid-page with
the page showing through. appendOnRoot portals it to the document root
so it actually covers the screen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…er on mobile
- The stacked leading row rendered in addition to the inline slot rather
than instead of it, so mobile showed two kind pickers. The inline slot
now yields when stackLeading is set.
- On mobile the schedule calendar moves up beside the header's scheduled-
posts clock, where scheduling already lives; the bottom bar keeps just
the Post button. Desktop keeps both beside Post.
Specs for this mobile round: toolbar leading-slot dedupe, the comment
drawer (full-screen, portaled, autofocus, fills, padding override),
keyboard-safe drawer sizing, backdrop-only close, header expand/schedule
placement per breakpoint, and the composer fills mode.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…loating bar on redesign
- Queue an early imperative focus until the TipTap editor exists, so reply
composers autofocus on desktop (the mount-time focus raced editor creation)
- Move the More-formatting tooltip onto the trigger's tooltip prop; the
Tooltip wrapper blurred the trigger on mouseup, insta-dismissing the menu
- Extract useOpenPostCommentRequest and register it in PostDiscussionPanel
too, so the mobile floating bar opens the composer on the redesigned post
page, not only the classic one
- Cover the full PR surface with tests: useVisualViewport, postComment event
bridge, Switch wrap fix, RichTextInput avatar/mode/focus, FooterWrapper
dispatch, PostPage integration, toolbar overflow menu
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Scrolling inside the mobile composer drawer chained to the post page
behind it, which visibly jumped and shifted. Apply the same body lock
react-modal uses (hidden-scrollbar, ref-counted for stacked drawers)
and contain overscroll on the drawer's own scroller; the composer's
internal scrolling is untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
<html> is the page's actual scroll container, so the body-level lock
never reached the viewport and the page behind the drawer kept
scrolling. Verified against the live preview.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The drawer's safe-area bottom padding stacked with the action bar's own
pb-5, reading as double spacing under the actions. The bar now absorbs
the safe area itself (max(1.25rem, safe-area-inset-bottom)) and the
drawer adds none, so the composer sits 20px from every edge.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The header and avatar hugged the comment list's 16px guideline while the
action bar carried 20px, so the drawer's edges did not match. In the
drawer there is no list to line up with, so both join the 20px frame the
action bar and create-post composer already use; inline keeps 16px.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Drawer: lock the page only for full-screen drawers, so context menus and
pickers keep the page scrollable exactly as before; capture and restore the
inline html overflow instead of deleting whatever another lock set
- useVisualViewport: take an `enabled` flag and subscribe only when asked;
non-full-screen drawers no longer re-render on every iOS keyboard scroll
- RichTextInput: fill the editor by flexing rather than `height: 100%`, which
resolved to auto where the box height comes from `minHeightClassName` and
let the editable collapse away from clicks below the first line
- CommentMarkdownInput: drop the inert `rows: 7`
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MyFeedPage › should request user feed timed out in test_webapp. Known
flaky async feed race — passes locally on repeat and this branch does
not touch the feed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Editing a shared link still opened the old /posts/[id]/edit page: the
menu only routed freeform and welcome posts to SmartComposerModal, and
the modal forced kind to 'text' whenever editPost was set, so it could
not represent a share at all.
- Derive the kind from editPost.type, prefill the commentary the way the
old page did (a share with no commentary carries the shared post's own
title, which is not the author's text), and seed the preview from the
post rather than refetching it
- Lock the URL while editing: only the commentary is editable, matching
what the API accepts
- Add the missing edit branch to submitLink, including the moderation
path, which completes without a toast of its own
- Route share posts to the modal; moderation items keep the page, which
edits a pending submission the composer cannot represent
- Delete CreateLiveRoomForm and CreatePoll, referenced by nothing
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercelBot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
daily-webappReadyReadyPreviewAug 5, 2026 6:27am
1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
storybookIgnoredIgnoredAug 5, 2026 6:27am

Request Review

Squad share triggers (ShareBar, DiscussionShareRow, Share modal) now open
SmartComposerModal seeded with the post as the link preview and the squad
preselected, replacing CreateSharedPostModal. The composer's link flow
already covers internal shares (preview.id -> addPostToSquad, moderation,
multi-squad via sharedPostId), so the old modal is deleted along with its
lazy-modal registration. A new onPosted callback carries the ShareToSquad
analytics event that the old modal's onSharedSuccessfully used to fire.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tsahimatsliahtsahimatsliah changed the title feat(composer): edit shared posts in the new composerfeat(composer): edit shared posts and share-to-squad in the new composerAug 4, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A composer opened pre-filled (share-to-squad, ?share= deep links) counted
its own seed as unsaved changes, so closing it untouched asked to discard
a draft the user never wrote.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tsahimatsliahand others added 3 commits August 5, 2026 00:43
Comments explaining what the code already says, narrating fixed bugs, or
restating test names are removed; the ones that survive state constraints
the code cannot show (platform quirks, cross-file contracts, do-not-readd
traps).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comments explaining what the code already says, narrating fixed bugs, or
restating test names are removed; the ones that survive state constraints
the code cannot show (platform quirks, cross-file contracts, do-not-readd
traps).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- share-edit save now shows its in-flight state (isUpdatePostLoading joins
isPosting)
- the mobile composer drawer portals to the root, so opening it from the
Share modal's drawer no longer traps it in a transformed ancestor
- editing always targets the post's own source instead of falling back to
the personal audience when the squad is no longer postable
- a moderated share edit is no longer logged as a post creation
- the share-edit dirty check trims like the seed comparison does
- a locked link with no surviving shared post renders no empty preview card
- CommentMarkdownInput skips the visualViewport subscription when it fills
its container (the value was discarded)
- the locked-link no-refetch spec now actually exercises the guard
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tsahimatsliahand others added 2 commits August 5, 2026 01:21
Live verification found two defects: a freeform post with no body (title +
cover) could never save a title edit because submit demanded a body, and
the desktop 'Add a comment' composer opened unfocused — the by-id focus
helper races the lazy chunk and TipTap's async editor, so the composer now
owns its focus through the queued autofocus path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tsahimatsliahand others added 4 commits August 5, 2026 09:10
- CommentMarkdownInput passes enabled=!fills to useVisualViewport, so the
mobile drawer stops re-rendering on iOS keyboard scroll for a value it
discards
- the toolbar overflow budget measures the rendered divider and the row's
column-gap instead of hand-synced constants (jsdom keeps fallbacks)
- the open comment composer owns its focus through the queued autofocus:
the by-id helper races the lazy chunk and TipTap's async editor
- a title-only freeform edit no longer demands a body to save
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tsahimatsliahand others added 3 commits August 5, 2026 09:20
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto 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

@tsahimatsliah