Commit Graph
100 Commits
Author SHA1 Message Date
Arjun Patel eb1e77b4c1 fix: allow next / prev particle during pause 2026-04-29 07:16:39 -07:00
Arjun Patel 1f2ab9b752 show keyboard hints for space pause 2026-04-29 07:16:16 -07:00
Arjun Patel a216b0722f fix: go to new local particle after creation
If human is on the last particle of the stream and creates a new one, this would navigate them there, as it makes sense to consider them caught up.
2026-04-29 06:49:13 -07:00
Arjun Patel f642d13ba1 feat: allow toggle stream-view play/pause 2026-04-29 06:16:24 -07:00
Arjun Patel 70087cf4e5 refactor: precondition for record toggle 2026-04-29 06:11:57 -07:00
Arjun Patel 0f73d685c2 refactor: narrow auth service to smaller units
Easier for testing, and omits need for full dep injection for pusher service, where we passed in nil for deps dangerously.
2026-04-27 16:14:37 -07:00
Arjun Patel 48d6d5cb07 refactor: unit testable units and cleaner dep injection 2026-04-27 16:07:28 -07:00
talksik 013c453dd3 chore: bump version 2026-04-25 14:25:42 -07:00
talksik 6b755a426b feat: add sound effects for hud actions
Closes #174
2026-04-25 14:25:24 -07:00
talksik 13c6bbd0fb refactor: re-structure stream playback control 2026-04-25 13:48:37 -07:00
talksik cf143f232d fix: prevent exit countdown during editing text 2026-04-25 13:10:32 -07:00
talksik a686d85e96 fix: papercuts around last particle in stream
Pause when holding space during close countdown. Prevent arrow keys
starting exit countdown when on last particle
2026-04-25 13:06:26 -07:00
talksik 03ced6d180 fix: pause playback during delete particle overlay 2026-04-25 12:49:01 -07:00
talksik 1c3b4447cf infra: hook up sentry 2026-04-25 12:43:30 -07:00
talksik 1e7a14d703 fix: set proper cluster name 2026-04-24 16:14:57 -07:00
talksik 0ac395ceb6 refactor: unused imports 2026-04-22 09:13:49 -07:00
talksik 110f4594be refactor: use shared mocks for tests 2026-04-22 09:06:10 -07:00
talksik 2e9ff742a0 send admin alert when new network created 2026-04-22 08:50:03 -07:00
64a21198b3 Add comprehensive error handling and recovery UI (#173)
* feat(electron): error handling foundation

Adds the infrastructure for a coherent client-side error story:

- `lib/errors.ts`: canonical ApiError + QuotaExceededError, `toUserMessage`
  (friendly strings for ApiError/ZodError/network errors, strips Electron
  IPC message prefixes), `logError` (expected), `reportError` (unexpected).
- `lib/query-client.ts`: QueryClient factory with sane retry defaults (no
  retry on 4xx except 408/429, 2 retries otherwise; 0 mutation retries),
  `QueryCache` onError logs + opts in via `meta.toastOnError`, and
  `MutationCache` onError toasts `toUserMessage(err)` by default with
  `meta.suppressToast` as the opt-out.
- `components/app-error-boundary.tsx` + `error-fallback.tsx`: two boundaries
  (top-level outside the router, route-level inside) with a Card-based
  fallback offering 'Go home' + 'Try again'. Route boundary resets on
  pathname change and clears React Query error cache on retry.
- `main/ipc-utils.ts` + main.ts migration: `safeHandle` wraps ipcMain.handle
  so main-process failures log with full stack and surface a sanitized
  message to the renderer. `link:fetch-metadata` keeps its null contract
  but now logs.
- `useCreateParticle` opts out of the global toast (compose-overlay renders
  its own quota UX) so nothing double-toasts.

Render crashes now have a recovery UI, every mutation gets a free error
toast, and silent-catch cleanup + Sentry land in follow-up PRs.

* refactor(errors): defer mutation errors to global handler

Now that MutationCache toasts via toUserMessage by default, the per-hook
onError duplicates drop away. Also normalizes inline query-error UI and
surfaces a previously-silent failure.

Mutations — removed redundant onError toasts:
- network-selector: useAcceptInvitation, createNetwork (inline)
- network-settings: useInviteMembers, useRevokeInvitation, useRemoveMember
- network-billing: useCreateCheckoutSession, useCreatePortalSession
  (onSuccess toasts stay — they carry domain context like network name)

Queries — consistent inline error UX via toUserMessage:
- network-selector: failed-to-load state gets a "Try again" button
- network-billing: "Couldn't load billing" includes friendly reason
- network-settings: useNetworkInvitations failure now surfaces a hint
  (previously rendered as "0 pending" — silently wrong)

Trimmed noisy JSDoc from PR 1 files (errors.ts, query-client.ts,
app-error-boundary.tsx, error-fallback.tsx, ipc-utils.ts).

* refactor(errors): route silent catches through logError/reportError

Every catch now either surfaces, re-throws, or calls logError with a scope
tag. No more empty catches or bare console.error:

- auth-store: signInToFirebase / restoreSession / signOut paths gain
  logError context. Behavior is unchanged (best-effort local sign-out,
  fall back to login on restore failure).
- use-stream-autoplay: Audio.play() and download-URL fetches log their
  failures instead of dropping silently (both are nice-to-haves so UX
  stays silent — but we can now trace "why didn't autoplay trigger?").
- pusher-client: ws errors / parse failures / server errors / listener
  crashes all routed through logError, and listener bugs (which silently
  break user flows) now go through reportError so they're actually
  surfaced in observability.
- settings-page: email-notifications toggle now toasts on failure
  instead of silently reverting with no explanation.
- huddle-app: screen-share failures use logError.

* feat(errors): add Sentry observability behind a sinks facade

logError / reportError now route through a pair of sinks installed at
bootstrap by each process (main + every renderer entry). No call site
knows about Sentry — if sentryDsn is empty, the sinks simply aren't
installed and logError/reportError stay console-only.

- appConfig.sentryDsn: per-env string (empty for now — populate when
  ops creates the DSNs). Empty is the no-op mode for dev.
- lib/errors.ts: installErrorSinks({ capture, breadcrumb }) gates
  Sentry.captureException / Sentry.addBreadcrumb. Everything flows
  through toUserMessage and the two existing call types.
- lib/sentry.ts: initSentryRenderer() for the main window + autoplay,
  huddle, and screen-record renderers.
- main/sentry.ts: initSentryMain() runs before anything else in
  main.ts to catch bootstrap failures. Captures uncaught exceptions
  and the crash reporter automatically.
- main/ipc-utils.ts::safeHandle now routes through reportError.
- main.ts::fetchLinkMetadata logs via logError.

* chore: cleanup reexports

* fix(errors): use relative import so main-process bundle resolves

Rollup in vite.main.config.ts doesn't know about the `@/` alias (only the
renderer configs do). After PR 4 made `lib/errors.ts` reachable from main,
its `@/config/env` import broke the main build with:

  [vite]: Rollup failed to resolve import "@/config/env" from "lib/errors.ts"

Swap to a relative import — the module is now genuinely isomorphic.

* build(vite): resolve @/ alias in main + preload configs

The four renderer vite configs all resolve "@" to ./src; main and preload
didn't, so any main-reachable file using @/ imports would break the
bundle. Mirror the same alias block in both configs and revert
lib/errors.ts + src/main/* to the project's @/ convention.

* fix invalid tsconfig field

* comment

* feat(errors): fallback handlers for unhandled rejections and errors

Sentry's global integrations natively capture window.onerror /
unhandledrejection (renderer) and uncaughtException / unhandledRejection
(main). When a DSN is configured we let Sentry own those paths to avoid
double-capturing. When it isn't (dev, unconfigured prod), we attach
minimal listeners that route through reportError so stray promise
rejections and uncaught errors at least hit the console and the facade.

This closes the one gap where a bare click-handler promise rejection
(no try/catch, not a useMutation) would otherwise be invisible.

* Revert "feat(errors): fallback handlers for unhandled rejections and errors"

This reverts commit ed84b29c6b.

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Arjun from Flowy Labs <[email protected]>
2026-04-20 19:22:02 -07:00
Arjun Patel bd7a0c4fb8 ci: remove unnecessary env variables 2026-04-20 17:41:02 -07:00
Arjun Patel 7bc06d1dc5 ci: add deployment workflows for different services 2026-04-20 17:29:33 -07:00
talksik 2deef1fe37 fix: prevent email notifications to removed members
Content will retain an older member in visible_to, even if that member
was removed from the network.
2026-04-18 07:37:07 -07:00
talksik 30469fda66 revert "fix: prevent writing to nonexistent firestore docs"
This reverts commit 46170eb5d3.
2026-04-16 18:00:51 -07:00
talksik 5e930d3c2e chore: bump version 2026-04-16 15:38:48 -07:00
98fdc98c76 feat: surface privacy policy and terms in sign-in and settings (#171)
Adds an implicit-consent disclaimer under the sign-in "Continue" button
and a new "Legal" section in the settings page. Both link out to the
policies hosted on flowylabs.ai via the existing openExternal bridge.

https://claude.ai/code/session_01U6gT7XFQ8Rtm3FStsHG63j

Co-authored-by: Claude <[email protected]>
2026-04-16 15:37:28 -07:00
Arjun PatelandGitHub ef899ee5cd security: access control for particles (#169)
* setup firebase custom token

* docs

* docs

* feat: allow admin removing members from a network

* fix: properly handle fallback avatar and names

This is especially helpful in the case of members who were removed from
a network
2026-04-16 15:14:34 -07:00
talksik 28b1ff542b feat: add support email
This will help humans whether they have billing questions or otherwise.
2026-04-16 09:15:31 -07:00
talksik 61a6904cc2 add nav bar to sign in flow 2026-04-16 09:14:50 -07:00
567884433d fix: render correct meta key in keyboard hints on Windows/Linux (#168)
Hardcoded ⌘ glyphs in compose hints were misleading on non-mac
platforms. Add a tiny platform helper sourced from the existing
window.electronWindow.platform bridge and substitute "Ctrl" off mac.
Closes #167.

https://claude.ai/code/session_01YZY4wUqACT7mgibHr2Yncx

Co-authored-by: Claude <[email protected]>
2026-04-16 09:04:29 -07:00
talksik 7da3ec6ab6 build: set windows icons properly 2026-04-15 13:32:53 -07:00
talksik ae38234e17 chore: bump version 2026-04-15 13:24:32 -07:00
talksik b508499dbd ci: fix no metadata for azure 2026-04-15 12:41:36 -07:00
talksik 111acb445c ci: fix duplicate signtool flags 2026-04-15 12:25:13 -07:00
talksik 6755ab9a1b ci: use newer sign tool for windows
The electron forge one won't support azure trusted account signing
2026-04-15 12:12:13 -07:00
talksik 772d8cf3e1 ci: enable debug for windows sign 2026-04-15 11:46:57 -07:00
talksik a03200472f ci: log windows sign failure 2026-04-15 11:30:32 -07:00
talksik 46170eb5d3 fix: prevent writing to nonexistent firestore docs
Using the same livekit project means we may receive events for another
environment, though we should eventually separate the environments.
2026-04-15 11:02:56 -07:00
talksik 17f84d5425 ci: adjust scripts to use correct file 2026-04-15 10:11:47 -07:00
talksik dd8e7da911 ci: setup windows with signing and gcs publish 2026-04-15 10:04:29 -07:00
talksik 07f02ecfeb use universal window controls across platforms 2026-04-15 08:03:32 -07:00
talksik 637342cb92 prevent remote release 2026-04-14 20:39:51 -07:00
talksik cee7958e03 ci: add preliminary windows build setup 2026-04-14 20:03:16 -07:00
talksik c2d0ed254b fix: improve messaging for reset
Closes #163
2026-04-14 19:04:54 -07:00
talksik 226b284ad2 fix: plan summary not refreshing after purchase 2026-04-14 19:04:31 -07:00
talksik f987f04ff0 chore: bump version 2026-04-14 15:20:55 -07:00
Arjun PatelandGitHub 67826b92c0 implement paywall (#161)
* implement core foundation

* inject deps

* fix incorrect migration

* tail migration

* use transaction for migration

* fix: inject deps for tests

* cleanup billing management for admin

* upgrade stripe sdk to v85

* set price env variables

* cleanup billing management

* allow multiple dev windows

* fix: settings scroll

* feat: show nice video thumbnail in listview

* feat: implement freemium restrictions

* remove unnecessary comments

* refactor

* docs

* format

* tweak network settings better hierarchy
2026-04-14 15:18:32 -07:00
talksik aff18d82db format 2026-04-14 10:59:53 -07:00
talksik fe2a003e94 fix: huddles not launching reliably
Closes #160
Race condition preventing proper initialization.
2026-04-14 07:58:45 -07:00
talksik 91a973b7fb chore: bump version 2026-04-13 10:44:40 -07:00
talksik 4e76ce902f fix: show proper app version 2026-04-13 10:41:19 -07:00
talksik ae91145762 feat: support url scheme to launch app
Closes #159
2026-04-13 10:41:11 -07:00
talksik f676baf8b3 cleanup video audio settings view 2026-04-13 10:39:58 -07:00
talksik 38bd098ca6 feat: select input devices for recordings
Closes #68
2026-04-13 10:29:10 -07:00
talksik 106254ef83 setup electron app for production 2026-04-13 10:03:23 -07:00
talksik 1579de8c6d feat: shift + arrows to seek track 2026-04-13 09:31:25 -07:00
talksik 3659863647 fix(orion): missing env variable 2026-04-13 09:29:44 -07:00
talksik 1cdaac66db show esc keybinding hint for recording 2026-04-13 09:14:22 -07:00
talksik f2b439cc86 fix: prevent stream exit during overlays 2026-04-13 09:11:46 -07:00
talksik 67091e9d13 fix: prevent exit countdown on playback blocked 2026-04-13 09:06:22 -07:00
talksik bf6fdd15b5 refactor: rename playback suspender 2026-04-13 09:03:24 -07:00
talksik 81a82eee38 fix: text message not showing attachments upon sending
Closes #154
The sender of the message wouldn't see the children particle since we
only fetch the children once. And perhaps the firestore local cache
doesn't have the children particles or some other race condition. But
now, we fetch the live children.

This should anyways use the same number of reads as before since
attachments do not change.
2026-04-13 08:59:34 -07:00
talksik bca45c5e89 chore: bump version 2026-04-12 14:16:01 -07:00
Arjun PatelandGitHub 5b6924f7ca feat: support image lightbox viewer and file download (#151)
* feat: add lightbox overlay with nice mechanics

* ui tweak for top bar in stream view

* support locally downloading attachments
2026-04-12 14:14:23 -07:00
talksik dbe61e447a fix: persist selected streams tab
This used to navigate me back to the open streams tab even if I click a
stream in the closed list
2026-04-12 13:44:35 -07:00
talksik af0a319399 docs: adjust claude.md 2026-04-12 13:34:49 -07:00
talksik 5a6db9bd48 fix: harden production electron app behavior
- No developer tools or zoom
- No refresh window
- Open links externally
2026-04-12 13:30:18 -07:00
e1d2bf11da feat: soft-delete particles within a stream (#149)
Lets a particle's creator delete their own message from the TopBar
dropdown. Other viewers see a "This particle was deleted" tombstone in
place and playback auto-advances after ~2s, keeping indices stable for
concurrent watchers.

- Add optional deleted_at / deleted_by_human_id to non-container
  particle variants and isParticleDeleted helper.
- Add softDeleteParticle Firestore helper.
- New DeleteParticleOverlay confirmation and DeletedParticleView
  tombstone.
- Hide reactions (bar + 1-7 keybinding) on tombstoned particles.
- Show "Deleted particle" + Trash2 icon in the stream list preview.

Closes #146

https://claude.ai/code/session_01M2ShnZPvWfQzzvuu3Xm8b9

Co-authored-by: Claude <[email protected]>
2026-04-12 13:18:10 -07:00
talksik 6b20fc2ee6 debug log 2026-04-12 13:16:46 -07:00
talksik be9cf6c5f0 fix: prevent stream click navigation during text edit 2026-04-12 12:15:59 -07:00
Arjun PatelandGitHub 7568ceb80d feat: extract text editor and allow editing (#150) 2026-04-12 12:13:25 -07:00
f03868e65b feat: paginate closed streams tab (#148)
Closed streams grow unbounded as teams age and can reach 1000s of items,
while open streams stay bounded by active work. Switch the streams list
to subscribe per-status so each tab only pulls what it renders, and cap
the closed tab to a 50-item window that grows via a Load more action.

The open tab remains unbounded so per-row realtime features — autoplay,
huddle indicators — keep full coverage of the streams that matter.
Closed streams are archived and don't need push behavior, which side-
steps the autoplay baseline-reset and huddle-on-page-2 risks that made
pagination complicated for the open tab.

Refs flowy-live/llink#138.

Co-authored-by: Claude <[email protected]>
2026-04-12 11:55:23 -07:00
talksik bea723a90a refactor: extract topbar component into separate file 2026-04-12 11:25:35 -07:00
talksik f64380c421 feat: manage stream name and members
Closes #99
Closes #28
2026-04-12 11:20:05 -07:00
talksik e58edaddd3 feat: send email notifications for network invites
Closes #126
2026-04-12 10:45:24 -07:00
talksik bfa970d706 fix: simplify window / app close behavior
Closes #142
2026-04-12 10:22:21 -07:00
talksik 484ee9d4fb fix: longer markdown content hidden
Added scroll to longer content and padding to avoid conflicting with
stream view peripheral controls and panels
2026-04-12 10:12:06 -07:00
Arjun PatelandGitHub 2bc4ce7060 fix: unreliable websocket functionality (#144)
* increases timeout

* refactor: reuse constants for redis db

* avoid closing connection on pong error

* fix: prevent electron from pausing timer executions

Our pings could be paused when our app is backgrounded.

* refactor: remove unnecessary refs

* fix: presence unreliable within same pod

It was broadcasting presence changes to other pods, but not notifying
clients connected to the same pod
2026-04-12 10:00:11 -07:00
talksik 4cb562e1ea chore: bump version 2026-04-09 17:05:45 -07:00
talksik 4d9a415981 fix: prevent playback marker going back in time
Closes #141
2026-04-09 17:02:50 -07:00
talksik 762c0ccf6e fix: emails sending despite online status
Closes #140
2026-04-09 16:30:07 -07:00
talksik 66aec4478f fix: websocket connections unreliable
Constant disconnection and reconnection every couple minutes or less.
2026-04-09 16:18:38 -07:00
talksik 5ea8476f54 debug logs for email notifier job 2026-04-09 15:31:45 -07:00
talksik 08f6b95b9b chore: bump version 2026-04-09 14:17:22 -07:00
talksik 51c4c6c822 feat: send email notifications for missed messages
- New cron job in cluster
- Handle presence and other concerns
- Toggle in app to disable email notifications
- Handles other edge cases such as cooldown period
- Simple html email with simple message

Closes #117
2026-04-09 14:15:34 -07:00
talksik 6fe5af8d8d fix: dock app badge shows incorrect number
Closes #122

We now only show number based on open streams
2026-04-09 12:31:14 -07:00
talksik d024751135 fix: avoid beep sound on text messages when autoplay disabled 2026-04-09 12:21:13 -07:00
talksik e9af91e0c3 fix: maintain consistent sort of stream members 2026-04-09 12:19:17 -07:00
talksik bea33d29d3 use green border for presence avatar 2026-04-09 12:17:55 -07:00
Arjun PatelandGitHub 3d8fa79657 add real-time infrastructure (#137)
* setup infra for pusher service

* setup client sdk for pusher service

* fix: ping parse failure

* fix: send pong back to client

avoid disconnections every 2.5 minutes

* increase replicas

* feat: show presence and compose indicator
2026-04-09 12:08:18 -07:00
talksik ce368c9e6e fix: broken screen clip playback progress
It wasn't progressing, likely because the metadata for a screen
recording was not read by the video html element. So we fallback to our
own duration metadata.

Closes #135
2026-04-09 11:00:55 -07:00
talksik 47b0d320f6 chore: bump version 2026-04-09 09:50:10 -07:00
talksik 0ab2a58f10 refactor: unused import 2026-04-09 08:42:29 -07:00
talksik 9cda5a3aa3 fix: two components rendering with same key
This is due to using binding.description as the key in Keybindings
overlay
2026-04-09 08:42:05 -07:00
talksik 3995777965 feat: toggle video / audio with keybinding 2026-04-09 08:40:27 -07:00
talksik d88d28a94e refactor: unused import 2026-04-09 08:31:23 -07:00
talksik 070684deb5 show overlay during screen recording 2026-04-09 08:31:23 -07:00
talksik f91068a4c1 fix: freezes during longer screen recordings
Closes #131

Fundamentally, we cannot afford to composite webcam and screen on the
client. We should create two streams and then combine them with ffmpeg
processing in the backend / orion.
2026-04-09 08:31:23 -07:00
talksik 5fadc6c8fe chore: bump version 2026-04-08 17:34:12 -07:00
talksik aaa0b1891f fix: focus lost when transitioning from immersive to markdown 2026-04-08 17:32:47 -07:00
talksik fbce997180 feat: position reactions in better spot
This prevents overlap with captions. It also introduces the laughing
emoji.
2026-04-08 17:25:50 -07:00