a fork of Bluesky app, with several features removed, and most importantly refactored to no longer
use React Native.

## development notes

this project uses React Compiler, `@base-ui/react` and Paraglide.

### project management

- Node.js and pnpm are managed by mise
- start the dev server with `pnpm dev` (vite)
  - always at http://127.0.0.1:19006
  - if it's already running, use it as-is
  - if port is used by another program: stop and report
  - if you need to stop a server you did not spawn for any reason, like stale HMR issues, confirm
    with the user.
- build with `pnpm build` (vite)
  - do not run `vite build` while a Vite development server is running, this will cause spurious HMR
    updates.
- format with `pnpm format` (oxfmt)
- lint and typecheck with `pnpm lint` (oxlint)
  - oxlint is configured to run the type checker, AVOID running `tsc`
- check `pnpm view <package>` before adding a new dependency

### browser automation

/agent-browser skill has been preconfigured with AGENT_BROWSER_NAMESPACE and AGENT_BROWSER_SESSION.
check `agent-browser session list` to see the assigned name and whether the browser is already
running for that particular session.

work that needs authentication should pass `--restore` when opening a session for the first time,
later commands may omit this flag. if you're still signed out, try going through the sign in flow
anyway but if it asks for a credential, stop and report to the user.

the agent-browser daemon and subsequently the browsers may close itself after some inactivity.

the first load of a page often comes up blank; reload once before you treat it as a bug. using curl
tests nothing of value.

do not run `agent-browser close --all`.

### code writing

#### formatting

- name new files in pascal-case for components and kebab-case for others.
- indent with tabs (spaces allowed for diagrams in comments)
- use single quotes for strings; reserve template literals for localization (user-facing strings,
  error messages)
- add trailing commas
- order list-like constructs (arrays, object keys, union/intersection members, enum variants,
  imports, etc.) by whatever meaning the list carries — a discriminant leads the object it tags, an
  external spec or call signature keeps its own sequence — and alphabetically when nothing does.
  - CSS properties go in outside-in concentric order.

#### control flow and structure

- use braces for control statements, even single-line bodies
- group related code and limit variable scope with bare blocks `{ }`
- use `switch` over `if`/`else if` chains when branching on a single discriminant value
- use `if` over ternaries for complex statements
- delimit sections of larger files with `// #region <name>` and `// #endregion`
- import directly from source; barrel files (index modules that re-export) are out

#### functions and methods

- prefer arrow functions; use method shorthand for object/class methods
- make a parameter optional only when callers genuinely split between passing a value and relying on
  the default. drop unused defaults; promote always-passed params to required; split into a separate
  function when presence/absence flips behavior
- prefer an options object when the function takes a boolean flag (split into two functions if the
  flag selects between distinct behaviors), when two same-typed params could be swapped at the call
  site, or when there are already 2+ optional/defaulted params. positional is fine at any count when
  each param has a distinct type and a clear semantic order

#### types

- write code that satisfies the type system naturally; reach for `as Type` or `as const` only when
  TypeScript errors and no cleaner solution exists

#### mutation

- treat function arguments as immutable; callers expect their inputs to come back unchanged.
  in-place operations like `array.sort()` or `Object.assign(target, ...)` are fine on values the
  function owns — locals, clones, freshly constructed objects — but copy first (`array.toSorted()`,
  `{ ...obj, ...patch }`) before touching anything reachable through a parameter. the exception is a
  function whose documented purpose is to mutate its argument; the name and JSDoc should make that
  intent obvious

### commit workflow

we use conventional commits with these rules:

- a commit represents one logical work
- accepted types: `feat`, `fix`, `refactor`, `docs`, `chore`
  - feat
    - new additions to public API surface
  - docs
    - Markdown document changes (README.md and similar)
  - chore
    - build/tooling/dependency changes
    - tests, code comments, or JSDoc changes
    - mass-autofixes from linters and formatters
- commit type describes the substance of the change as a whole, not a category to split it by. tests
  written for a feature ship in the `feat` commit; `chore` applies when test, comment, or JSDoc work
  is the entire change
- no scopes; write `feat: ...` / `refactor: ...`, never `feat(runtime): ...`
- append `!` after the type to mark breaking changes, e.g. `feat!:` or `refactor!:`

### documentation

"documentation" here means READMEs, code comments, and commit messages.

- write in lowercase, except for proper nouns, acronyms, and 'I'. public-facing interfaces (web UI)
  are exempt
- comment non-trivial code only, focusing on _why_ rather than _what_
- add JSDoc to new publicly exported functions, methods, classes, fields, and enums:
  - `@param` for parameters (no dashes after param names)
  - `@returns` for return values
  - `@throws` for exceptions when applicable
  - document the caller-facing contract: _what_ the API does, not _how_. implementation details
    (anything that could change without breaking callers) and _why_ explanations belong in regular
    code comments; the exception is rationale that constrains callers, like a precondition, which
    stays in the JSDoc
  - keep descriptions concise but informative

### agentic coding

- `.research/` in the project root is an ephemeral workspace for experiments, analysis, and planning
  materials, and may contain cloned repositories or other reference materials that inform
  implementation decisions. create if not present. its contents are local to a single working copy
  and may be wiped or absent in any future session, so committed source code (including comments,
  docstrings, commit messages, READMEs, or other docs) must not reference paths under `.research/`
  or rely on its contents existing
- this document is intentionally incomplete; discover everything else by exploring the repo
- explore the code first when unsure about plans, requirements, or existing behavior. ask for
  clarification when exploration leaves the question unresolved
- when debugging, isolate the root cause before attempting fixes: add logging, reproduce the issue,
  narrow down the scope, and confirm the exact source of the problem
- find and read code yourself with Grep/Glob/Read — they locate things directly and return the real
  source. a subagent hands back only its paraphrase of what it saw, so you'd have to verify it
  against the source anyway, and the round-trip rarely pays off: even 'where does X live?' is
  usually one search away. reserve subagents for genuinely large parallel sweeps
