01App shell · final hashtag rule 02Auto-share contract 03Note menu · viewer roles 04Multi-group note 05Invite + acceptance 06Group directory 07Promote tag · backfill preview 08Cross-user tag conflict 09Real Slack data · populated stream 10Phone · responsive frames 11Decision · eager vs lazy 12Tint reconciliation
01 App shell · final inline-hashtag rule (A on first occurrence, C on repeats) First #competitor gets the IF glyph · second #competitor in the same note shows as underlined only
IF
IdeaFlow Team
8 members · 214 notes · you are an owner
JJacob Coleyou 2 hours ago

Lattice intro call. They build the same hashtag capture as us, but scoped to HR. IF#competitor for sure — they pitched at SHRM. Could also be a IF#customer for OEM-ing the editor.

Their #competitor angle: they own the HR ops vertical. Our #competitor angle: we have the better editor primitive. Mutual interest seems real.

Action: send API docs to @sarah review by Tuesday.

IFIdeaFlow Team ·2 replies ·shared via #competitor
SSarah Kim yesterday

Heads-up — pushing the sprint review back to Thursday. Conflict with the board prep. @jacob @david please confirm.

IFIdeaFlow Team ·shared directly
JJames Park ↗ Slack · #biz Apr 12 2024

also #biz #wins awesome IF#customer IF#userinterview with Founder's Pledge — 70-person org pays $30k/yr for Salesforce. Inline-tag UX was the deciding factor. #customer wants to expand to IF#salesforceuser integration.

IFIdeaFlow Team ·4 thread replies ·shared via #customer + #userinterview
02 Auto-share contract · directly-shared vs tag-shared notes Distinct note-foot pills make the source of group membership obvious
A · Directly shared
JJacob Coleyou 2h

Sprint review moved to Thursday — board prep conflict.

IFIdeaFlow Team · shared directly
Source: NoteSharing(GROUP, ideaflow_team)
Created by: author explicitly picked the group in the share dialog
Remove from group: "Remove from IdeaFlow Team" in ⋯ menu — clean detach, note disappears from group stream
Author control: only the note's author can attach/detach
B · Auto-shared via team tag
JJacob Coleyou 2h

Lattice intro. IF#competitor for sure.

IFIdeaFlow Team · shared via #competitor
Source: author tagged #competitor; tag has defaultGroupId = ideaflow_team
Created by: eager — NoteSharing(GROUP, ideaflow_team) row written at note save time (see Frame 11)
Remove from group: two options in ⋯ menu — "Remove tag #competitor" (clean — removes the cause) OR "Untag from group, keep #competitor" (creates a sticky exclusion row)
Author control: only the note's author
Why the distinction matters: the reason pill tells the reader why this note is in the group's stream. Without it, "shared directly" vs "auto-shared via tag" become invisible — and removal UX silently differs. The pill is also the affordance: clicking #competitor in the pill jumps to the tag detail page.
03 Note menu · varies by viewer's relationship to the note Only the note's author can change sharing — group "owners" cannot curate someone else's note in v1
Architectural origin: note-sharing-service.ts:39 hard-codes findUnique({ where: { id: noteId, authorId }}). Loosening this for group owners is a meaningful auth-model change. v1 doesn't do it.
04 Multi-group note · one note in multiple group streams Compose left · how the note appears in each group's stream right
Share dialog · 2 groups picked
Share this note
Groups
IFIdeaFlow Team× FFamily×
11 people will see this note (8 in IdeaFlow + 3 in Family, minus 0 overlap).
People
Schema · what gets written
INSERT INTO note_sharing (note_id, principal_type, principal, role)
  VALUES
    ('0193-abc', 'GROUP', 'ideaflow_team_id', 'VIEWER'),
    ('0193-abc', 'GROUP', 'family_id',       'VIEWER');

UPDATE notes SET is_shared = true WHERE id = '0193-abc';
Same note appears in both group streams
IF IdeaFlow Team 214 notes
… earlier notes …
JJacobyou just now

Booked an Airbnb in Lake Tahoe for the offsite weekend. Same place as last year. F#trip

IFIdeaFlow Team + FFamily ·shared directly to both
… earlier notes …
F Family 8 notes
… earlier notes …
JJacobyou just now

Booked an Airbnb in Lake Tahoe for the offsite weekend. Same place as last year. F#trip

IFIdeaFlow Team + FFamily
Identical note rendered. The foot shows both groups regardless of which stream you're viewing — readers always see the full audience. Order: current group first.
05 Invite + acceptance · 3-state journey Principals are EMAIL — invitees may not have accounts. Show all three states clearly.
1Sender adds an email

Jacob types teresa@startup.io in group invite. Teresa doesn't have a Notestream account yet.

T
teresa@startup.io
no Notestream account
Pending

Schema: GroupInvite(EMAIL, "teresa@startup.io") + GroupMember row deferred until acceptance.

2Recipient receives email
3Recipient accepts

Two flows depending on whether they sign up:

✓ New user signs up
Lands on Notestream sign-up with IdeaFlow Team pre-filled. On first login, GroupMember(group, user) row created. EMAIL→USER auto-resolved by matching User.email.
⇄ Existing user accepts
In-app notification with Accept/Decline:
IF

Jacob invited you to IdeaFlow Team · 8 members

Visibility before acceptance: the EMAIL principal in NoteSharing already grants visibility (matches the existing model — see getVisibilityWhere). So Teresa can see group-shared notes from the moment Jacob invites her. Membership formalizes for sidebar + composer suggestions.

06 Group directory · the public side of discoverable groups For groups marked "Discoverable in directory" in create dialog (Frame 04 in v2)

Discover groups

Public groups you can join or follow. Private groups don't appear here.

IF
IdeaFlow Team
Internal knowledge stream — product, customers, competitive, hires.
8 members·214 notes·2h ago
L
Lattice Eng Talks
Public notes from Lattice engineering — talks, postmortems, architecture decisions.
312 members·89 notes·yesterday
BC
SF Tech Book Club
Reading 1 nonfiction book per month. April: Designing Data-Intensive Apps.
47 members·156 notes·3 days ago
FP
Founders Pledge
Effective philanthropy research notes from FP team.
22 members·78 notes·5h ago
YC
YC W26 Founder Notes
Shared notes from the W26 batch — customer learnings, pivot stories.
184 members·421 notes·10m ago
DG
Design Guild
Designers sharing critique-ready work and process notes.
58 members·92 notes·yesterday
Join vs Follow: "Join" requires owner approval and adds you as a member (you can post). "Follow" gives read-only access if the group allows it — the group stream appears in your sidebar under a "Following" sub-section.
07 Promote tag · backfill preview with histogram + author list Refined from v2 frame 06 · the preview makes "backfill 23 notes" concrete instead of abstract
Move #strategy to IdeaFlow Team
This tag is currently private. 23 of your notes use it. Preview below.
Notes that will be backfilled
Oct 2022spans 24 monthstoday
23 notes · oldest from Oct 14 2022 · spike in mid-2024 around the seed raise.
Sample of what will be shared
Pricing · Aug 12 2024

Three-tier pricing — Free / Pro $10 / Team $80. #strategy

Why we're not building a wiki · Jul 4 2024

Stream-first beats tree-first because tagging defers structure. #strategy #designphilosophy

Seed raise thesis draft · Jun 18 2024

Why now — LLM-era unbundling of Notion. #strategy · ⚠ contains @mentions of confidential investors

Lattice partnership memo · May 3 2024

Why OEM-ing the editor unlocks…

Backfill scope
1 note flagged — "Seed raise thesis draft" mentions confidential investors. Review before backfilling, or exclude.
8 members will gain access to 23 notes.
08 Cross-user tag namespace · per-user SharedHashtag reality `SharedHashtag(userId, hashtag)` is keyed PER USER · each member has their own tag rows
The schema keys SharedHashtag by (userId, hashtag). Each user has their own SharedHashtag rows. So #competitor can have different group attribution depending on which user authored the note. Three concrete cases below.
J Jacob's #competitor
defaultGroupId = ideaflow
→ IdeaFlow Team
Jacob promoted his tag to the team. All his IF#competitor notes auto-share to IdeaFlow.
S Sarah's #competitor
defaultGroupId = null
→ private
Sarah kept her version private. Her #competitor notes don't share to any group.
J James's #competitor
defaultGroupId = ideaflow
→ IdeaFlow Team
James also promoted. His IF#competitor notes auto-share. So 14 + 21 = 35 of the 46 total #competitor notes in IdeaFlow Team are from Jacob + James.
Autocomplete when Sarah types #comp
Just talked to Acme — their existing tool burned them. #comp
🔒
#competitor
11 notes · private to you
IF
#competitor
used by IdeaFlow Team · Jacob + James share theirs
💡 If you adopt the team binding, your future #competitor notes also share to IdeaFlow.
UX consequence: autocomplete should expose both the user's private tag and any group-adopted versions that group members are using. The "Join" affordance is the low-friction path — adopting the group binding writes defaultGroupId on Sarah's row without forcing her to navigate to settings.
09 Real IdeaFlow Slack data · populated stream (post-import) Actual tag-cooccurrence patterns from the export — #bug + #p1, #customer + #userinterview, etc.
IF
IdeaFlow Team
9 members · 2,880 notes (+ 14,302 thread replies) · imported from Slack 2 days ago
📌 group: IdeaFlow Team × source: Slack · × newest first
JJacob Coleyou ↗ Slack · #biz Apr 12 2024

also #biz #wins awesome IF#customer IF#userinterview with Founder's Pledge today — 70 person org currently pays $30k/year for salesforce, dropped them after our trial. Deciding factor was the inline-tag UX. IF#salesforceuser

IFIdeaFlow Team ·4 thread replies ·shared via #customer + #userinterview + #salesforceuser
JJames Park ↗ Slack · #mew-product Jul 4 2023

filed IF#bug IF#p1 — drag-to-reorder breaks if you drag onto a collapsed parent folder. Need to fix before launch.

Repro: collapse Folder A, drag Note B onto A's row. State doesn't update.

IFIdeaFlow Team ·3 thread replies ·shared via #bug
DDavid Liu ↗ Slack · #competitors Mar 2 2024

Roam dropped a new "smart blocks" feature this morning. Different framing than ours but solves the same atomic-capture problem. IF#competitor

IFIdeaFlow Team ·1 reply ·shared via #competitor
SSarah Kim ↗ Slack · #general Nov 9 2023

Met Anika at the conference — strong PM background, ex-Notion, looking. Asked great questions about our atomic-capture thesis. IF#potentialhire

IFIdeaFlow Team ·2 thread replies ·shared via #potentialhire
JJacob Coleyou ↗ Slack · #design Jan 18 2024

v9 sidebar design done — testing the expand-tag pattern. IF#design IF#designphilosophy

Key insight: tree navigation is too rigid for our model. Tags-as-collapsible-streams is the right primitive.

IFIdeaFlow Team ·5 thread replies ·shared via #design
+ 2,875 more imported notes · spans Jan 2015 → Feb 2026
10 Phone · responsive frames at iPhone 15 Pro Max width (~390px) Groups become a tab bar item · sidebar collapses to a sheet · note actions become a bottom sheet
9:41
📶 🔋
Groups
IF
IdeaFlow Team
2h ago · Jacob: "Lattice intro…"
3
S D J
Sarah, David, James
yesterday · David: "see attached"
F
Family
3 days ago · Mom: "Sunday dinner?"
BC
Book Club
last week · 5 new notes
FP
Founders Pledge (ext)
2 weeks ago · 1 new
📓Notes
🏷Tags
👥Groups
🔍Search
You
9:41
📶 🔋
IF
IdeaFlow Team
📌 IdeaFlow × 214
Jyou 2h

Lattice intro. IF#competitor for sure — could OEM the editor.

via #competitor
SSarah yday

Sprint review → Thursday. Board prep conflict. @jacob confirm.

JJames ↗ Slack '24

Founder's Pledge interview. IF#customer

via #customer
📓Notes
🏷Tags
👥Groups
🔍Search
You
9:41
📶 🔋
Note
Jyou 2h ago

Lattice intro. IF#competitor — could OEM.

shared via #competitor
11 Decision · eager vs lazy materialization of tag→group sharing Eager writes NoteSharing(GROUP) rows at note save · Lazy joins through SharedHashtag.defaultGroupId at query time

Eager recommended

At note save time (or tag-add time), if the tag has defaultGroupId set, write a NoteSharing(GROUP, groupId) row immediately.

Pros

  • Visibility resolution is unchanged — getVisibilityWhere already handles principalType branching. Group becomes one more value.
  • Removing the tag from a note can leave the share intact (or remove it — explicit author choice in ⋯ menu)
  • "Show what's in this group's stream" = one indexed query against NoteSharing
  • Audit trail: every share has a row with timestamp
  • Author retains explicit control — can detach without removing tag

Cons

  • If group binding changes later (#competitor → demoted to private), existing NoteSharing rows stay stale — need a backfill migration
  • Slack import writes ~17k NoteSharing rows up front (manageable)
  • Tag removal needs a UX choice: also remove share, or keep?

Lazy alternative

No NoteSharing rows for tag-auto-shares. At query time, getVisibilityWhere joins SearchIndexContent.hashtagsSharedHashtag(defaultGroupId=X) at every read.

Pros

  • Single source of truth — change defaultGroupId and the group stream updates instantly
  • No backfill needed when binding changes
  • No NoteSharing row sprawl

Cons

  • Query path gets harder — adds a 3-way join (SearchIndexContent × SharedHashtag × GroupMember) on every visibility check
  • Author can't detach a single note from a group without removing the tag
  • Audit trail: no per-note shared-at timestamp for tag-shares
  • Sentinel "explicit exclusion" rows still needed for "this note has #competitor but I don't want it shared this once" → adds back complexity eager avoids
Verdict: Eager. Notestream's visibility model is already row-based — going lazy means special-casing the group path. Author control is also clearer with explicit rows. Migration cost (rewriting NoteSharing rows when defaultGroupId changes) is a one-time backfill, handled by a background job. Same model as how Linear / Notion handle "template-derived ACLs."
12 Tint reconciliation · existing shared-hashtag tint + new team-glyph Commit bb2747a already tints notes carrying shared hashtags · Groups extend, don't replace

Today (pre-Groups)

Jyou 2h

Lattice intro. #competitor

Tint + left-edge stripe = "this note carries a SharedHashtag." No author attribution on the tag itself.

Groups v1 (new)

Jyou 2h

Lattice intro. IF#competitor

via #competitor
Same tint (continuity). Adds: (1) team glyph on first tag occurrence (2) reason-pill in foot. Existing users see continuity, new info is additive.

Edge case: tag shared to PERSON (not group)

Jyou 2h

Recipe for tonight. #dinner

shared with S Sarah
Tag is shared to a person, not a group. Still tinted (it's a shared hashtag). No team glyph — underline only. Foot shows the human recipient.
Rule: Tint = "this note carries any shared hashtag" (unchanged). Team-glyph = "the tag has defaultGroupId" (new, additive). Reason-pill in foot tells you why it's shared. Three independent signals layered cleanly.
File: mockups/groups-v3.html · v1 and v2 untouched.