01App 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 Coleyou2 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 Kimyesterday
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 · #bizApr 12 2024
also #biz#wins awesome IF#customerIF#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
02Auto-share contract · directly-shared vs tag-shared notesDistinct note-foot pills make the source of group membership obvious
A · Directly shared
JJacob Coleyou2h
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 Coleyou2h
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.
03Note menu · varies by viewer's relationship to the noteOnly the note's author can change sharing — group "owners" cannot curate someone else's note in v1
A · You wrote this note
Full menu. Attach/detach groups, change privacy, delete.
↗Share…⌘⇧S
📋Copy link
Shared with
IFIdeaFlow Team✓
FFamily
+New group…
🔒Make private
🌐Publish publicly
📌Pin
🗑Move to trash
B · Group member, not author
You can read, reply, copy. Sharing controls are disabled with explanation.
💬Reply
📋Copy link
Shared with
IFIdeaFlow Team✓
Only Jacob (author) can change this note's sharing. Ask them to add other groups.
🔕Mute thread
🚫Hide from my streamlocal
C · Group owner, not note author
Same as B in v1 — group owners do NOT have curate authority. v2 might add "Request author to remove from group."
💬Reply
📋Copy link
Shared with
IFIdeaFlow Team✓
Owning a group doesn't let you remove notes from it. Only the note's author can. v2 candidate: request removal.
🔕Mute thread
🚫Hide from my streamlocal
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.
04Multi-group note · one note in multiple group streamsCompose 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).
UPDATE notes SET is_shared = true WHERE id = '0193-abc';
Same note appears in both group streams
IFIdeaFlow Team214 notes
… earlier notes …
JJacobyoujust 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 …
FFamily8 notes
… earlier notes …
JJacobyoujust 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.
05Invite + acceptance · 3-state journeyPrincipals 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
J
Jacob Cole
via Notestream · to teresa@startup.io
You're invited to IdeaFlow Team
Hey Teresa — added you to our IdeaFlow Team group on Notestream. It's where we share customer interviews, competitor research, and design notes.
Notestream is a personal note-taking app. You'll create an account in 30 seconds. Learn more
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.
06Group directory · the public side of discoverable groupsFor 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.
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.
07Promote tag · backfill preview with histogram + author listRefined 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.
08Cross-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.
JJacob's #competitor
defaultGroupId = ideaflow → IdeaFlow Team
Jacob promoted his tag to the team. All his IF#competitor notes auto-share to IdeaFlow.
SSarah's #competitor
defaultGroupId = null → private
Sarah kept her version private. Her #competitor notes don't share to any group.
JJames'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.
09Real 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 · #bizApr 12 2024
also #biz#wins awesome IF#customerIF#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-productJul 4 2023
filed IF#bugIF#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 · #competitorsMar 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 · #generalNov 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 · #designJan 18 2024
v9 sidebar design done — testing the expand-tag pattern. IF#designIF#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
10Phone · 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
SDJ
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
Jyou2h
Lattice intro. IF#competitor for sure — could OEM the editor.
11Decision · eager vs lazy materialization of tag→group sharingEager 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.hashtags ∩ SharedHashtag(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."
Tint + left-edge stripe = "this note carries a SharedHashtag." No author attribution on the tag itself.
Groups v1 (new)
Jyou2h
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)
Jyou2h
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.