You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add Huddle Conversation Topics With Stand-Up Workdays, Team Conversations and Direct Messages - #871
Implements #866. Huddle posts now belong to a conversation topic with a permanent ID instead of a calendar date. A workday runs from one Start huddle to the next, so a 2 AM post still counts for the workday it was part of. Teams can also hold named team conversations and direct messages. The composer shows where every post goes, and warns before sending one that won't show in the thread being read.
Built in five commits, one per milestone of the implementation plan, plus one that removes Day. Each commit stands on its own.
What changed
Commit
Milestone
6b74015d
M1 · Conversation records and backfill.huddleConversations and huddleConversationLabels, plus conversationId on every post. An idempotent startup backfill maps old posts onto one closed "legacy" workday per team and day. It never invents a stand-up time.
0c215f88
M2 · Workdays.huddle.startWorkday closes the open workday and opens one for the starter's local date. createHuddlePost resolves and checks the conversation for every new post: the composer, plans, wrap-ups, Pulse, timer updates and scheduled delivery.
0750cfe1
M3 · Team and direct conversations.huddle.conversations.list/create/rename/setMyLabel. Private posts carry audienceIds, and every read filters on them: REST feeds, the live publication and its change stream, ticket views and notifications.
15232228
M4 · Inbox. Group by → Conversation, a Start huddle button, + to create a team conversation or direct message, and ⋯ to rename for everyone or just for me. All @mieweb/ui.
b00efb58
M5 · Composer. "To: Team › Conversation" next to the message box. A warning before sending a post that won't show where you're reading. A workday that closes mid-draft keeps the draft and says why.
9efffcd7
Day removed. Group by offers All, Conversation, Session, Person and Ticket. Huddle opens in Conversation. Old Day links and landings go to the matching conversation.
Every new Meteor method is exposed over REST in main.js, so the mobile app can call it.
Race conditions handled
These follow the plan's R1–R23: a unique index, insert-then-read-the-winner on a duplicate key, or a conditional update. No locks are needed.
Starting a huddle: two people starting at once, or a post landing mid-start, still end with one open workday ({ open: true } partial unique index).
A workday closing mid-draft: the composer always names its destination explicitly, so the server refuses a closed workday instead of quietly moving the post.
Server-side posts (Pulse, timer updates, clock-out recovery) are redirected to the open workday, since they have no draft to give back.
Scheduled posts get their workday in the same update that claims the delivery.
Direct messages: two people opening one at the same moment get a single conversation (unique pair key).
Private posts never leak to people outside the conversation, including in a live feed opened before the conversation existed.
Testing
Backend unit tests: meteor-backend/tests/conversations-core.test.ts, 32 tests.
Backend integration tests: conversations.test.ts and conversations-access.test.ts, 32 tests, including the concurrent races. Run several times in a row.
Frontend unit tests: grouping, empty threads, destination and warning rules, and the closed-workday error message. All 924 pass.
New browser tests in tests/e2e/huddle/conversations.spec.ts: the warning when sending from a closed workday, Cancel keeping the draft, and a workday closing mid-draft. The existing inbox-filters, clock-lines and mounting specs were updated for Conversation replacing Day.
Full npm run test:all: 452 of 468 browser tests passed.
Still failing, not caused by this PR:tickets/video-upload.spec.ts › "a browser WebM is attached and served as an MP4". The server stores video/webm. This branch doesn't touch uploads, PulseVault or tickets.
Already failing on main:huddle-post-rest.test.ts › "stores each supported format…".
Checked by hand on the dev server, desktop and phone width: Start huddle, creating a direct message and posting in it, rename for me, the send warning (Cancel, and "Send and open it"), and a workday closing mid-draft.
Decisions for the reviewer
Day is gone; Conversation is the default (9efffcd7). Day and Conversation showed nearly the same threads. A saved Day choice falls back to Conversation, an old day:YYYY-MM-DD link opens that date's workday, and a post sent from Session while clocked out opens its conversation (huddle.createPost now returns conversationId). The 'day' grouping still exists inside superChatFeed.ts, where about 30 unit tests use it as a fixture; removing it there is a follow-up.
Workday dates use the starter's local date. There's no team timezone in the codebase. dateKey is checked against POST_DATE_RE, must be within a day of server time, and can't be earlier than the open workday.
Who can start a huddle: any team member. Rename for everyone: the creator or a team admin. No open workday when someone posts: one opens automatically.
Not in this PR
Comments, likes, edits and deletes on a private post still only check team membership, so someone with the post's id could act on it. This needs a small follow-up requirePostAccess check.
A teammate sees a workday someone else started once the first post in it arrives, not the moment it starts.
The new controls haven't been tested keyboard-only. The send warning's Cancel was tested with text, not attachments.
Prototype-only UI ideas from the issue discussion are deliberately left out.
Adds huddleConversations and huddleConversationLabels with their indexes,
and a stable conversationId on every Huddle post.
- conversations-core.js: pure rules (access, open/closed, names, direct-pair key)
- conversations.js: indexes, legacy workdays, and an idempotent startup backfill
that maps existing posts onto one closed workday per team and day, with no
invented stand-up time
- New posts (composer, Pulse, wrap-up, timer updates) join the legacy workday
for their date; a scheduled post gets its conversation in the same update
that delivers it
- The feed returns conversationId; nothing changes on screen yet
… M2)
- huddle.startWorkday({ teamId, dateKey }) closes the open workday and opens
one for the starter's local date; starting again the same day is a no-op.
dateKey must be a real date within a day of the server's and not before
the open workday's, so a device with the wrong date can't close today's
- openWorkday is the one way a workday opens: an { open: true } partial
unique index keeps one open per team, and a duplicate-key loser returns
the winner, so concurrent starts and posts never make two (R1-R3)
- createHuddlePost resolves and checks every post's conversation: on the
team, visible to the author, open. The composer is refused a closed
workday (conversation-closed) so its draft stays; server-side posts
(Pulse, timer updates, wrap-up recovery) go to the open workday instead
- Scheduled posts bound for a workday join the one open at delivery,
written in the same update that claims the delivery
- A legacy day for the same date is reopened rather than duplicated
- huddle.startWorkday and createPost's conversationId exposed over REST
…#866 M3)
- huddle.conversations.list/create/rename/setMyLabel, each over REST too.
A direct conversation is unique per pair (insert, then read the winner on
a duplicate key); a team conversation has a shared name its creator or a
team admin can change once; anyone can set a name only they see
- Cindy sees her direct conversation with Bob as "Bob" and Bob sees it as
"Cindy": one record, names worked out per reader in two queries
- A post in a team or direct conversation carries its members as
audienceIds, and every read filters on the post itself: getPosts,
getMyPosts, getPostsByTicket, the huddlePosts.byTeam publication and its
change stream. A conversation made after a page loaded can't leak (R21)
- Notifications for a private post (followers, @team/@here, @mentions,
mention retries) reach its members only
…nversations (#866 M4)
- Group by gets Conversation: one thread per workday, team conversation and
direct message, titled with the name the reader sees. A workday past
midnight shows day dividers. Open topics with no posts yet (a workday just
started, a direct message just made) get an empty thread to post from
- useConversationTopics loads the team's topics and refetches when a post
names one it doesn't know (R22); conversationThreadId/topicIdOfThread map
between thread and topic ids
- Start huddle, a + to create a team conversation or direct message, and a
conversation menu to rename for everyone or for me, all @mieweb/ui
- A post sent from a conversation thread goes into that conversation; a
scheduled one bound for a workday is placed at delivery
- Day stays the default grouping until #866's open question is decided
- Release note in 1.2.0
… won't show (#866 M5)
- "To: Dev Team › Thu, Nov 12" beside the message box names the team and
conversation every post goes to; tapping it opens that conversation
- Every send names its conversation explicitly (R5): a workday that closed
while the post was written is refused, the draft stays, and the message
says the next huddle started; the topics reload so the new workday shows
- Before sending a post that won't show in the thread being read (a closed
workday or another conversation, another person's or ticket's thread, or
a search it won't match), an AlertDialog says why; Cancel keeps the draft,
"Send and open it" posts and opens where it went. Day and Session need no
warning: they already open the thread a post lands in (#854)
- e2e: tests/e2e/huddle/conversations.spec.ts (closed workday warning, Cancel,
workday closing mid-draft)
- Release note
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Unresolved issues can expose private posts to former team members, publish private replies team-wide, and create future-dated workdays that block legitimate huddles.
Adds persistent conversation topics to Huddle, replacing date-only post associations with stand-up-based workdays, named team conversations, and direct messages while retaining existing inbox groupings.
Day and Conversation showed nearly the same threads, so Day goes: Group by
offers All, Conversation, Session, Person and Ticket, and Huddle opens in
Conversation. A saved Day choice falls back to it.
- An old ?conversation=day:YYYY-MM-DD link opens the workday for that date
- A link to an off-the-clock post opens the conversation that holds it, and
a post sent from Session while clocked out opens its conversation:
huddle.createPost now returns the conversationId it used
- A post without a conversation (only before the backfill reaches it) waits
in "Earlier updates" under a key that doesn't move when the topic list
loads; only a not-yet-delivered scheduled post shows in the open workday
- e2e: Day assertions moved to Conversation; the scroll test seeds one past
workday per date (seedWorkdayPost)
- Release note
Check search visibility using inbox grouping logic
src/features/huddle/superChatFeed.ts:300
This check warns that a search hides the new post even when the inbox keeps it visible: searching a conversation's title retains the whole thread, including replies that do not repeat that title. All also searches ticket, date and media fields omitted here. Evaluate the prospective post or thread with the same grouping-specific search logic as the inbox, supplying its conversation and post metadata.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements #866. Huddle posts now belong to a conversation topic with a permanent ID instead of a calendar date. A workday runs from one Start huddle to the next, so a 2 AM post still counts for the workday it was part of. Teams can also hold named team conversations and direct messages. The composer shows where every post goes, and warns before sending one that won't show in the thread being read.
Built in five commits, one per milestone of the implementation plan, plus one that removes Day. Each commit stands on its own.
What changed
6b74015dhuddleConversationsandhuddleConversationLabels, plusconversationIdon every post. An idempotent startup backfill maps old posts onto one closed "legacy" workday per team and day. It never invents a stand-up time.0c215f88huddle.startWorkdaycloses the open workday and opens one for the starter's local date.createHuddlePostresolves and checks the conversation for every new post: the composer, plans, wrap-ups, Pulse, timer updates and scheduled delivery.0750cfe1huddle.conversations.list/create/rename/setMyLabel. Private posts carryaudienceIds, and every read filters on them: REST feeds, the live publication and its change stream, ticket views and notifications.15232228@mieweb/ui.b00efb589efffcd7Every new Meteor method is exposed over REST in
main.js, so the mobile app can call it.Race conditions handled
These follow the plan's R1–R23: a unique index, insert-then-read-the-winner on a duplicate key, or a conditional update. No locks are needed.
{ open: true }partial unique index).Testing
meteor-backend/tests/conversations-core.test.ts, 32 tests.conversations.test.tsandconversations-access.test.ts, 32 tests, including the concurrent races. Run several times in a row.tests/e2e/huddle/conversations.spec.ts: the warning when sending from a closed workday, Cancel keeping the draft, and a workday closing mid-draft. The existinginbox-filters,clock-linesandmountingspecs were updated for Conversation replacing Day.npm run test:all: 452 of 468 browser tests passed.inbox-filtersfailures came from this PR's warning blocking sends from an older Session thread. Fixed: Day and Session don't warn, because they already open the thread a post lands in (fix(huddle): posts made while clocked in join the live session; Session shows shifts only #854). Those specs pass on rerun.tickets/video-upload.spec.ts› "a browser WebM is attached and served as an MP4". The server storesvideo/webm. This branch doesn't touch uploads, PulseVault or tickets.main:huddle-post-rest.test.ts› "stores each supported format…".Decisions for the reviewer
9efffcd7). Day and Conversation showed nearly the same threads. A saved Day choice falls back to Conversation, an oldday:YYYY-MM-DDlink opens that date's workday, and a post sent from Session while clocked out opens its conversation (huddle.createPostnow returnsconversationId). The'day'grouping still exists insidesuperChatFeed.ts, where about 30 unit tests use it as a fixture; removing it there is a follow-up.dateKeyis checked againstPOST_DATE_RE, must be within a day of server time, and can't be earlier than the open workday.Not in this PR
requirePostAccesscheck.🤖 Generated with Claude Code