v4.35 — Teams V1.1 — Sub-communities
A team member can now open a room they belong to and read/post to its feed — the last of four sub-community stubs v4.19 honestly gated instead of built, though three of the four turned out to already be shipping elsewhere by the time this milestone was scoped.
Summary
v4.19 Phase 4 closed out the Teams V1 wiring audit by honestly gating eight controls that looked enabled but silently no-opped — an IOU with a ticket number on each one, rather than a control that pretended to work. Four of those eight shared one surface, the team sub-community (room): OBJ-1825 room "Open feed," OBJ-1824 room "Edit," OBJ-1822 the Members-tab room filter, and OBJ-1823 "View team activity." v4.35 set out to pay off all four as a 3-phase milestone.
It shipped as one phase. A mid-milestone scope collision, caught while dispatching Phase 2, found that three of the four source tickets were already shipping independently on master, on a stream this milestone had no visibility into. Kickoff was written at 06:44 CDT; OBJ-1822 merged 67 minutes later (07:51), OBJ-1824 merged 2h21m later (09:05), and OBJ-1823 merged 6h06m later (12:50), while this milestone's own Phase 1 was still in development. All three landed after kickoff, not before it — an earlier version of this page, corrected after review, had the sequence backwards for two of the three. The real gap wasn't the census ref; at the moment of kickoff none of the three tickets existed on any ref yet. It was that nobody re-censused between kickoff and Phase 2's dispatch attempt roughly nine hours later, on a repo where independent streams ship fast. Phases 2 and 3 were cancelled before any executor was dispatched, and the milestone collapsed to Phase 1 — the room feed — plus the integration work needed to land it alongside the independently-shipped work.
The room feed itself was cheap to build because a team sub-community was never a new model: Teams::CreateSubCommunity creates a plain Community row with team_id set, so the entire feed, comment, reaction, and moderation stack already existed. Phase 1 reused /communities/:publicId with an additive team-room mode rather than building a parallel view, and inherited a fresh constraint from CommunityAccessPolicy (shipped independently just before kickoff, closing a live gap where a seated team member could read/post to a gated room by direct API call): the room feed is offered only to members, with Join shown instead on an open room a user hasn't joined yet.
Goal
Tapping "Open feed" on a room the user belongs to opens that room's feed. Archived rooms open read-only for the remainder of their 30-day retention window. The two
disabled: true/· soongates inroomMenuButtonsare removed.—
.planning/milestones/v4.35-teams-v11-sub-communities-ROADMAP.md, Phase 1 goal
Scope — What Shipped
- Team-room mode in
views/Community.vue, additive and gated strictly oncommunity.teamIdpresence — public (non-team) community rendering is byte-identical to before, and every pre-existingCommunity.vuetest passed unchanged. - Both ⋮ branches wired in
TeamSubCommunitiesTab.vue: an active room a member belongs to offers "Open feed"; an archived room a member belongs to offers "Open feed · read-only" with no composer and no reaction/comment affordance; a non-member sees "Join room" on an open room and nothing on a gated one — never a feed entry that would 403. - A server-side gap closed that the plan hadn't anticipated: archived rooms could still be posted to server-side even though the client suppressed the composer.
Social::CreateCommunityPostgained arequire_not_archivedguard. - Close-out integration landing three independently-shipped tickets alongside Phase 1's own work:
master's already-shipped OBJ-1822 (Members-tab room filter, PR #2201), OBJ-1824 (room edit, PR #2207), and OBJ-1823 ("View team activity," PR #2208) were merged into the integration branch and reconciled against Phase 1's changes to the sameTeamSubCommunitiesTab.vuefile — a semantic conflict in the sharedroomMenuButtonscomputed, not a textual one — before the whole branch promoted tomaster. docs/architecture/community-authorization.md, a new centralized statement of the OBJ-2302 membership rule and the archived-room write boundary this surface depends on.
Phases
| Phase | Name | Status | Plans | Highlights |
|---|---|---|---|---|
| 1 | Room feed (OBJ-1825) | Shipped | 3 | Team-room mode, both ⋮ branches, archived-room posting guard. PR #2210, 27 checks green. |
| 2 | Room edit (OBJ-1824) | Cancelled, superseded | — | Shipped independently to master via PR #2207, 2h21m after this milestone's kickoff. |
| 3 | Sub-community-scoped reads (OBJ-1822 + OBJ-1823) | Cancelled, superseded | — | OBJ-1822 shipped independently via PR #2201, 67 min after kickoff. OBJ-1823 shipped independently via PR #2208, 6h06m after kickoff. |
Key Decisions
- A team sub-community is not a new model.
Teams::CreateSubCommunitycreates a plain,team_id-scopedCommunityrow, so Phase 1 reused the existing feed, comment, reaction, and moderation stack via an additive team-room mode on/communities/:publicIdinstead of building a parallel view — avoiding duplicating that entire pipeline for zero functional gain. - The non-member "open room" state renders Join, never a feed entry that would 403.
CommunityAccessPolicy(shipped independently just before this milestone's kickoff) requires an actualCommunityMemberrecord for any team room, closing a prior live gap where a seated team member could read/post to a gated room by direct API call. Rather than loosening that policy to make a "Join CTA from inside the feed" pattern work, Phase 1 decided membership up front from team-query data and offers Join before the feed, not from within it. - Checked master's independently-shipped implementations against this milestone's own acceptance criteria, rather than assuming partial coverage. When the scope collision was found, master's OBJ-1824 implementation was traced against the Phase 2 task package's stated risks — including the cross-team
lead_is_team_member?guard — and confirmed complete before cancelling the phase, rather than redoing verified work on the strength of "we didn't build this." - Collapsed to a 1-phase milestone rather than re-planning Phases 2 and 3 around what had already shipped. A milestone that delivered exactly what it ended up promising is scored against that collapsed scope, not the original plan — recorded explicitly in the ROADMAP and this milestone's audit so a future reader doesn't mistake the collapse for an incomplete milestone.
- The
roomMenuButtonsmerge was resolved as a semantic conflict, preserving both changes rather than picking one. Phase 1's membership branching and master's realEdit roomimplementation both needed to land in the same computed property; the close-out merge grafted master'sEdit roominto both active-room branches where Phase 1 had left a· soonplaceholder, rather than letting one side's git-merge auto-resolution silently drop the other's logic.
Requirements Coverage
5 / 5 ROADMAP success criteria satisfied — see the Milestone Audit for the full table. This milestone has no standalone REQUIREMENTS.md; coverage is derived from the ROADMAP's "Success criteria for the milestone" section, per this repo's convention for hand-authored milestones.
| Criterion | Status |
|---|---|
All four gated controls live, no disabled/· soon/ComingSoonPill | Satisfied |
docs/product/phase-7-teams.md gating table lists none of the four as gated | Satisfied |
| No enabled control can read/write a room the user isn't entitled to | Satisfied |
| Public (non-team) communities surface behaviorally unchanged | Satisfied |
Integration branch promoted, check-branch-promotion.mjs contains exits 0 | Satisfied |
Outcomes
All four sub-community-surface IOUs v4.19 Phase 4 gated are now live: a team member can open a room's feed, read/post to it (or read-only if archived), an owner/admin can edit a room's details without archive-and-recreate, the Members-tab room filter actually filters, and "View team activity" opens a real read-only activity view. Three of the four shipped through a different, unplanned stream — the product outcome is identical to what this milestone set out to deliver, but the delivery record honestly reflects that only the room feed (and the integration work to reconcile it with the rest) came from this milestone's own phases.
Tech Debt
- (Deliberately out of milestone scope, recorded by the ROADMAP) OBJ-2326 — the parent-team eyebrow needs a small GraphQL field addition.
- (Deliberately out of milestone scope, documented in
community-authorization.md) OBJ-2347 —AddPostCommenthas no room-access guard. Pre-existing, broader than this milestone (a comment path, not the post path OBJ-2302 fixed). - (Adjacent, not this milestone's scope) A fourth v4.19 Phase 4 gated stub, OBJ-1826 (leaderboard scope switcher), shipped independently via PR #2220 while this milestone was in flight — same pattern as the three superseded tickets above, different ticket.
- (Process gap, not a code defect) At close, OBJ-1825 was found
cancelledin the issue tracker while its code was merged and awaiting promotion, and OBJ-1822/OBJ-1823 werein_progresswhile their code was already onmaster— both out of sync with reality. Not corrected as part of this milestone's docs close.
Related Artifacts
- Roadmap: v4.35-teams-v11-sub-communities-ROADMAP.md
- Milestone Audit: v4.35-teams-v11-sub-communities-MILESTONE-AUDIT.md
- Authorization contract this milestone depends on: docs/architecture/community-authorization.md
- Triggering issue: OBJ-1825, epic confirmed by Josh via Maggie ("Epic is good go 4.35")
- Shipped phase PR: #2210
- Superseding PRs (built outside this milestone): #2201 (OBJ-1822), #2207 (OBJ-1824), #2208 (OBJ-1823)
- Follow-ups: OBJ-2326 (parent-team eyebrow field), OBJ-2347 (comment-path access guard)
Related Commits
3b54bb32b— Phase 1: room feed, team-room mode + honest ⋮ menu gating (PR #2210)b319ad82c— Close-out: merge master into integration branch, resolveroomMenuButtonsconflict, promote tomaster(OBJ-2353)
Last updated: 2026-08-09 · Version: v4.4.5