* Add first-run onboarding wizard for recording mechanics
Introduce a minimal, skippable first-run wizard that teaches the record
key. On first authenticated launch it renders as a full-screen takeover
(gated above PusherProvider) so the stream/compose keyboard handlers are
not mounted to compete for the same keys.
Steps: welcome, hold ` to record, tap V to switch audio/video, tap ` to
toggle recording, mic/camera permission priming, and a recap. The lessons
detect the real gestures and never block — Esc or the Skip control
dismisses at any point.
Reuses existing pieces: VideoAudioToggle, useMediaDevices().requestLabels
for permission priming, KeyHint, and isTypingTarget. Extracts the shared
HOLD_THRESHOLD_MS into lib/constants and reuses it in the compose overlay.
Resolves#259
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A9AHySdLb1zfZAEm6SuaQP
* nits
* light mode support for onboarding
---------
Co-authored-by: Claude <noreply@anthropic.com>
* mobile: add invitations, network creation, and settings (parity phase 1)
Surface backend capabilities that already existed in the mobile API client
but had no UI:
- NetworkListScreen now lists pending invitations with an Accept action and
a header "+" to create a network; empty state offers creation instead of
pointing users to desktop.
- New CreateNetworkSheet and use-invitations hooks (accept invite, create
network) following the existing react-query patterns.
- SettingsScreen replaces its placeholder with an email-notifications toggle
(optimistic, mirrors desktop), app version, and sign out.
- Wire the previously-unreachable Settings row into the Drawer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqGPzXQ1AA9CqYHgCgmbtV
* mobile: network member management and avatars (parity phase 2)
- New NetworkSettingsScreen (reachable from the stream-list header) lists
members with admin remove, an invite-by-email sheet, and pending
invitations with revoke — backed by new use-member-management hooks.
- Avatars: add avatar_object_id to HumanSchema, uploadAvatar/deleteAvatar/
getAvatarDownloadUrl client methods (raw PUT via expo-file-system), a
use-avatar-url hook, and image rendering in the shared Avatar component.
AccountScreen gains a tap-to-change profile picture via expo-image-picker.
- auth-store gains refreshUser to pick up avatar changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqGPzXQ1AA9CqYHgCgmbtV
* mobile: task particles — view, edit, and compose (parity phase 3)
Bring the task particle to parity with desktop's richer model:
- Replace the thin `quest` schema with desktop's `task` model
(ChecklistItem, TaskProperties: title/notes/checklist/assigned_to/done)
in the discriminated union, the Firestore converter, and consumers
(StreamCard, FallbackParticleView).
- New TaskParticleView renders an editable card (round done checkbox,
title, notes, checklist with add/toggle/edit/remove, assignee chips)
persisting each edit to Firestore; an 8s dwell auto-advances and
field focus suspends playback. Wired into StreamView's render switch.
- Compose: a task button in the ComposeDock opens a TaskComposeSheet
(createTaskParticle helper). Gated off in the new-stream flow, where a
stream's first particle must be text or media.
Note: particles are written client-side to Firestore, matching desktop;
Orion's REST validator still only accepts `quest`, which is a pre-existing
inconsistency to reconcile backend-side separately.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqGPzXQ1AA9CqYHgCgmbtV
* mobile: paper and file particle views (parity phase 4)
- Extract the shared markdown renderer/theme out of TextParticleView into
a reusable MarkdownBody component (DRY).
- PaperParticleView renders desktop-authored documents (title + markdown)
with a length-based dwell.
- FileParticleView shows name/size and a Download action that opens a
signed URL via the OS.
- Both wired into StreamView's render switch; FallbackParticleView is now a
true catch-all for unknown/folder types only.
Deferred (documented for a follow-up phase): composing papers/files from
mobile, particle attachments + lightbox, and link previews in text.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqGPzXQ1AA9CqYHgCgmbtV
* mobile: billing & usage in network settings (parity phase 5)
Surface the network plan, daily usage, and Stripe management — all backed
by client methods that already existed. New use-billing hooks and a
BillingSection (mirroring desktop): every member sees the plan + usage
summary; admins get cadence selection + "Upgrade to Pro" (checkout) and
"Manage subscription" (portal), opening Stripe in the system browser.
Added to NetworkSettingsScreen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqGPzXQ1AA9CqYHgCgmbtV
* mobile: let users dismiss the keyboard from a task card
Focusing a task field opened the keyboard with no way out — it covered the
card and the stream's tap-to-advance zones. Now:
- A "Done" pill appears at the card's top-right while editing (reusing the
existing `editing` flag) and calls Keyboard.dismiss(); the title row
reserves space so the pill never overlaps a long title.
- The card ScrollView gains keyboardDismissMode (interactive on iOS, on-drag
on Android) so dragging the card also dismisses the keyboard.
Dismissing blurs the active field, which flips `editing` off and resumes the
dwell timer and tap navigation automatically.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqGPzXQ1AA9CqYHgCgmbtV
* decrease clutter in stream-view
* format
* format
* consolidate avatar
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(desktop): immersive focus mode for editing tasks
Editing a task in the stream was ambiguous: nothing signalled that you
were editing, playback chrome stayed live, Escape exited the whole stream,
and arrow keys could navigate to another particle mid-edit.
Focusing any field now enters a focus mode: the surrounding stream chrome
dims and blurs while the task card lifts above it with a soft ring, so the
task you're editing is unmistakable. Escape (or clicking outside the card)
leaves the editor and resumes playback from where it paused instead of
exiting the stream, and arrow/list keys no longer pull focus to another
particle while editing.
Text particles already get this via their full-screen edit overlay, so the
two editing flows are now consistent.
Addresses #286
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BDWBJjERKk8zUmh6bcKWSM
* refactor(desktop): make task focus mode self-contained
Handle editing focus entirely within TaskParticleView instead of threading
editing state up to StreamView and special-casing the navigation hook.
While editing, the card now registers a capture-phase key listener — the
same pattern the app's other focus overlays use — that consumes Escape
(blurring the field, which flushes the draft and resumes playback) and
swallows stream-navigation keys so they can't pull focus to another
particle. An "Esc to finish" hint shows while editing.
This restores use-stream-navigation-keys to its original form and reduces
the StreamView change to a single `immersive` prop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BDWBJjERKk8zUmh6bcKWSM
* fix style of keyhint
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix: route clipboard image pastes to the attachment strip
Pasting an image while the markdown editor was focused left it stuck on
ProseMirror's inline "uploading" placeholder instead of going to the
attachment strip. The capture-phase paste interceptor bailed before it
could redirect the file because:
- pasted images usually surface only through `clipboardData.items`
(`getAsFile`), with `clipboardData.files` left empty, and
- image pastes often advertise an *empty* `text/plain` entry, which the
old `types.includes('text/plain')` check mistook for a text paste.
Read files from `items` (falling back to `files`), and gate on the actual
text payload rather than the advertised type, so real text pastes
(Excel/Word renditions) still paste as text while pure image pastes
become attachments.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtNQeBpkDJPC9obiB25pV6
* fix: disable Milkdown ImageBlock so paste never strands an upload node
Routing pasted images to the attachment strip wasn't enough — the editor's
ImageBlock feature still handled the paste itself and left a stuck "upload
in progress" node, since there's no public upload URL for it to resolve to.
Turn the feature off entirely. It's the root cause: it's the only thing
that registers paste/drop-to-upload. Image *links* still render through the
base commonmark schema (a plain <img>), and pasted image files continue to
go to the attachment strip via use-file-input. Drop the now-dead image-block
upload/chrome CSS and keep a simple inline-image style.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtNQeBpkDJPC9obiB25pV6
* fix: decline dropped files in the editor so they don't inline
Dragging an image onto the markdown editor attached it to the strip (the
drop zone fired) but ProseMirror also handled the same drop, parsing the
drag's HTML into an inline image node with a blob:/localhost src. That URL
then leaked into the markdown and surfaced as a stray link-preview chip.
Add a ProseMirror handleDrop that declines drops carrying files, so the
editor stops inlining them while the drop zone still routes them to the
attachment strip. Extract the paste/drag file extraction into a shared
transferFiles helper used by both paths.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtNQeBpkDJPC9obiB25pV6
* nit
* format
---------
Co-authored-by: Claude <noreply@anthropic.com>
While recording or reviewing a compose, Escape both cancelled the compose
and exited the stream. The stream's window-level navigation handler skips
keys when an input is focused (isTypingTarget), which is why the text path
was unaffected, but recording/reviewing have no focused input so Escape
leaked through to the exit handler.
Fix it at the compose layer rather than teaching the navigation pipe about
compose: the compose key handler now consumes (preventDefault +
stopPropagation) any key it handles and listens in the capture phase, so it
reliably wins over the stream's bubble-phase navigation/action handlers
regardless of listener registration order. Pure pause states (hold-space)
are untouched, so Escape still exits the stream there.
This leaves the typing/task/configuring paths equivalent (their onCancel is
the same cancel() the handler invokes) while ⌘+Enter / ⌘+M still fall
through to the text editor.
Claude-Session: https://claude.ai/code/session_01DapjFX1MhYPZeJv4s56K5L
Co-authored-by: Claude <noreply@anthropic.com>
* fix: avatar showing blank when switching
* fix: sidebar padding glitchy
Long markdown content would make each chat row expand and remove the padding in the sidebar
* ignore emacs project.el
* nit
* first attempt at stream sidebar, tasks, and events
* fix folder from root
* cleanup folders and events, and condense changes
* cleanup and add toggle for sidebar
* cleanup
* fix nits
Electron Forge restarts the renderer process without fully restarting the Vite dev server, so the browser gets a fresh page with a new hash but the Vite server still has the old pre-bundle cache — mismatch causes the 504 outdated error every other time we launch.
* paginate for open and closed streams
* fix: add platform abstraction for deeplink (#251)
* fix: add platform abstraction for deeplink
This was crashing App.tsx due to the deep link listener calling methods on window which do not exist.
* fix: apply CodeRabbit auto-fixes
Fixed 1 file(s) based on 1 unresolved review comment.
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
* log error
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
* cleanup
* fix: import
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
* fix: add platform abstraction for deeplink
This was crashing App.tsx due to the deep link listener calling methods on window which do not exist.
* fix: apply CodeRabbit auto-fixes
Fixed 1 file(s) based on 1 unresolved review comment.
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
* log error
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
* security: add cors for desktop app scheme
* Revert "security: add cors for desktop app scheme"
This reverts commit d450fced75.
* ignore tags
* add commands for windows dev
* cleanup unnecessary parts of main desktop process
* resolve lint errors
* add format command for go
* fix: send email on new member joining
Closes#228
* add proper deeplinking on desktop
The desktop app was merely focusing before, but now it will navigate to the proper route
* honor email notifications setting
* prevent sending email when no recipients
* update copy for sign in views
This disambiguates whether the view is for sign up or sign in.
* format
* escape character
* format
* update workflow names
* Use inline WYSIWYG markdown editor for long text messages
Desktop: replace the Write/Preview tab toggle in the compose card with
MDXEditor, an inline WYSIWYG that renders markdown as you type (Obsidian/
Notion feel) and round-trips plain markdown, matching how particle content
is stored. Immersive mode is unchanged — short, plain notes still get the
centered large-type textarea, and ⌘+M still drops into the rich editor.
Shortcut handling moves to the capture phase so ⌘+Enter / Esc win over the
editor's own key handling.
Mobile: render markdown on the display side via react-native-marked's
useMarkdown hook (no FlatList, so it nests cleanly in the existing
ScrollView). Mirrors desktop's immersive-vs-card logic: short plain notes
stay centered; anything with markdown renders formatted instead of showing
raw syntax. Composer stays plain text.
https://claude.ai/code/session_019DU6V5z6Nr4Vu7b1fBDnq4
* cleanup desktop app description
* Fix markdown editor rendering and pasted-link duplication
- Restore heading/list typography in the editor: MDXEditor relies on the
browser's default styles for <h1>/<ul> sizing, which Tailwind's Preflight
resets. Markdown shortcuts fired but the block looked unchanged. Style the
editor content explicitly, mirroring the rendered display.
- Keep pasted/typed URLs as plain text (linkPlugin disableAutoLink) so they
don't become `[url](url)`, which duplicated both the URL and its preview
card. Also dedupe extractUrls defensively so a repeated URL yields one card.
https://claude.ai/code/session_019DU6V5z6Nr4Vu7b1fBDnq4
* Complete GFM support: tables, task lists, strikethrough
Editor: add tablePlugin so markdown tables round-trip and render. Task
lists (`- [ ]`), strikethrough, code (inline + fenced), headings, lists,
quotes, and links already work via listsPlugin/core/markdownShortcut. CSS:
suppress the disc bullet on task-list items (they draw their own checkbox)
and style tables.
Display: render task-list checkboxes (no stray bullet), GFM tables, and
strikethrough so the read view matches what the editor produces.
https://claude.ai/code/session_019DU6V5z6Nr4Vu7b1fBDnq4
* Unify markdown on one engine (Milkdown Crepe)
Replace the two-engine setup (MDXEditor for editing + react-markdown for
display) with a single Crepe-based component used for both. Editing is inline
WYSIWYG with full GFM that actually works — task lists, tables, and code
blocks via type-to-create / the slash menu — and read-only mode renders the
exact same way, so write and read can no longer drift (the root cause of the
heading/list/table/checkbox bugs).
- markdown-editor.tsx: Crepe wrapper supporting edit + readOnly, themed to the
glass card via --crepe-* variable overrides (scoped to .milkdown so they win
over frame-dark's own definitions).
- text-particle-view.tsx: render the message card with the read-only editor;
drop the react-markdown component map.
- Remove now-unused deps: @mdxeditor/editor, react-markdown, remark-gfm,
rehype-highlight, highlight.js.
The compose immersive mode (short, centered plain text) is unchanged.
https://claude.ai/code/session_019DU6V5z6Nr4Vu7b1fBDnq4
* Fix Crepe slash-menu transparency and match the app font
The floating menus (slash menu, toolbar, link tooltip) portal to <body>,
outside .milkdown, so the --crepe-* overrides never reached them and their
background fell back to transparent — unreadable over the content. Declare the
palette on those selectors too, keep --crepe-color-surface (near-)opaque so the
menus read clearly, and add a backdrop blur.
Also set --crepe-font-default/title to inherit (and a monospace stack for code)
so the editor uses the application font instead of Crepe's bundled Noto faces.
https://claude.ai/code/session_019DU6V5z6Nr4Vu7b1fBDnq4
* Give the slash menu the full card by padding the editor, not the card
Crepe appends the slash menu to .milkdown and (in this version) doesn't expose
a way to portal it to <body>, so it's clipped by the editor's scroll box. That
box sat inside the card's p-5 padding, so the menu cut off at the padding edge.
Move the padding onto the ProseMirror content instead and drop the redundant
inner scroll container, so the menu's clipping bounds become the full card.
https://claude.ai/code/session_019DU6V5z6Nr4Vu7b1fBDnq4
* tweaks
* mobile: render markdown text consistent with desktop
---------
Co-authored-by: Claude <noreply@anthropic.com>