Commit Graph
33 Commits
Author SHA1 Message Date
pastilhasandClaude Opus 5 10ff23c5dd gitea: the app — repos, code, issues, pulls, notifications
Replicates what Gitea's own web UI offers, on top of the sidecar's /_api pass-through.

Repository browsing (tree, file view with the FileViewer's shiki renderer, README), commits,
branches, tags, releases, issues and pull requests both per-repo and cross-repo, notifications,
explore/search and organizations. Routes are /gitea/:section plus /gitea/repo/:owner/:name/:tab/:item,
all real Links with the URL as the source of truth — no selection channel.

Markdown is rendered client-side (react-markdown + remark-gfm + rehype-sanitize, rehype-raw
deliberately absent) rather than through the instance's /api/v1/markdown, because consuming that
means dangerouslySetInnerHTML and there is no DOMPurify in the tree with installs frozen. The cost
is Gitea's #123 and @mention cross-references; relative links and images are resolved instead.
The /markdown and /markup allow-list entries stay, so that door is open when a sanitiser lands.

retargetUrls rebases instance-minted URLs onto a browser-reachable origin, IN ONE DIRECTION ONLY.
This instance answers with two: /user and /repos build from its configured ROOT_URL
(http://localhost:9004), /contents from the public host. An unconditional rewrite onto the
connection URL therefore broke the second set to match the first, turning working https links into
dead loopback ones. Only a private/loopback URL is rewritten now, and only when the target is
itself public; when the connection URL is a dial address nothing is touched and the connection
screen says why avatars will not load.

Also carries the frontend half of the one-instance-many-tokens model: the connection form draws a
URL field only for the owner and sends no url key at all for anyone else, ServiceConnection.url is
string | null to match officerdb, and the rebase origin comes from the resolved instanceUrl rather
than connection.url, which is null for a member.

Not verified: no runtime pass since the last four changes, the issues and pull views have never
rendered a row (the instance has none), and the member path has never executed (one account).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 17:10:36 +00:00
pastilhasandClaude Opus 5 a894d64cb3 jellyfin panel app: browse, detail and player
adds the /jellyfin screen and its two panels (nav + view) on top of the
jellyfin sidecar, following the photos/invoiceshelf shape.

the transcode path is a progressive mp4 rather than hls, so no hls.js
dependency is added under the frozen-install rule. that stream has no
length and no byte ranges, so the player owns its own scrubber and seeks
by re-negotiating at a new startTimeTicks, tracking offset + currentTime;
a direct file keeps native controls. the hls url is still returned, so
switching later is a player change only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 18:16:45 +00:00
pastilhasandClaude Opus 5 4d2ec215a2 add /calendar and /contacts screens over the caldav json door
two workspace screens backed by one app folder: the collection list on the
left, the selected calendar's agenda or address book on the right. both read
the officer-caldav sidecar through the /api/caldav auth proxy, which holds no
DAV credentials of its own.

the selected collection is a DAV path with slashes in it, so it lives in
?collection= on the same route rather than as a path segment — still in the
URL, still bookmarkable, no selection channel. an absent or unknown value
resolves to the first collection inside the panels instead of redirecting,
because until the list has loaded there is no canonical URL to redirect to.

the agenda is a grouped list, not a month grid: the sidecar returns RRULE
unexpanded, so a grid would have to invent occurrences the server never
claimed existed. a repeating event gets a badge instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 10:50:53 +00:00
pastilhasandClaude Opus 5 035a1ba8f6 add photos, an immich-backed library behind its own sidecar
officer-photos owns the whole Immich contract: the instance URL and the API
key live there and nowhere else, and the platform side is an auth-gated
forwarder holding no credentials. The route surface is an allow-list keyed on
the first path segment, so admin, auth, api-keys, sessions, jobs, system-config
and libraries are unreachable by construction rather than by enumeration.

The UI mirrors Immich's own sidebar — timeline, explore, map, search, albums,
people, favorites, sharing, archive, trash — because the point of a sidecar
screen is to reproduce what the upstream already ships, then extend it. The
timeline reads Immich's columnar time-bucket format directly; selection lives
in the URL per docs/navigation-audit.md.

Two things worth knowing for anyone touching this later:

- `duration` is an integer count of milliseconds in Immich 3.0. It was an
  HH:MM:SS.mmm string before, and every stale example still shows that form.
- the map container is sized with h-full/w-full, never `absolute inset-0`.
  maplibre's stylesheet sets `position: relative; overflow: hidden` on the
  element it is given, and an unlayered vendor rule beats Tailwind 4's layered
  `.absolute` regardless of source order — so the div collapses to height 0 and
  clips its own canvas away. Nothing errors: the GL context is healthy, tiles
  download and pixels are drawn into a buffer nobody ever composites.

maplibre-gl is pinned to 5.x deliberately; 6.0 resolves a separate worker file
from import.meta.url, which Officer's index.html fallback answers with HTML.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 00:18:33 +00:00
pastilhasandClaude Opus 5 82290bb3e0 add /qr-transfer — offline file transfer over animated QR
TEMPORARY / EXPERIMENTAL, at the owner's request, after deedy/qr-data-transfer (QRFerry).
Two panels: one loops a file as QR frames, the other scans them through the camera and
rebuilds it. Entirely client-side — nothing about a transfer reaches the server, which is
the point of the technique.

NOT RaptorQ, and that is the one real design decision here. QRFerry carries RFC 6330
fountain-coded symbols so a receiver can rebuild from ANY sufficient set of frames. This
uses a plain indexed carousel instead, for two reasons — the second being the deciding one:

  1. RFC 6330 is days of work and unpleasant to debug.
  2. The sender and receiver are being reimplemented on iOS and Android. A format one person
     can re-derive from protocol.ts in an afternoon is worth more here than optical
     efficiency. Every frame is independent, self-describing, and parses with a string split.

The cost is honest and written down: without fountain coding you must eventually capture each
specific frame, so a miss waits for the next pass rather than being covered by surplus. Fine
for a few hundred KB on a steady camera; it degrades where RaptorQ would start to pay for
itself.

Details that matter for the phone implementations:
- base64url, no padding — ':' and '/' would collide with the field separator.
- CRC-32 of the whole file in the meta frame, checked after reassembly. The test pins the
  reference value for "123456789" (cbf43926) so any stock implementation will agree.
- The meta frame repeats every 12 frames, so a receiver joining late learns the filename and
  total without waiting a full cycle.
- Error correction level L: frames are short-lived and repeated forever, so QR capacity is
  better spent staying sparse enough to scan than on recovery.

16 tests over the protocol, which is the spec the other implementations should match.
The receiver needs a secure origin for camera access; over the tailnet with HTTPS that holds,
and it says so plainly rather than failing silently on plain http.

Adds qrcode and jsqr. @types/qrcode was already present and orphaned — its runtime package had
been removed with the chat-channel cleanup.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 02:17:20 +00:00
pastilhasandClaude Opus 5 f8826e4c24 add the bitcoin wallet sidecar and ui
the owner's work, committed as one unit rather than split: the registration
files (App.tsx, Dock, AppRegistry, hono.ts, the schema and db barrels,
ecosystem.config.cjs) all reference modules under src/servers/{api,sidecar}/wallet
and src/workspaces/officerdev/src/apps/Wallet, so committing the shared plumbing
on its own would leave a commit that does not build.

officer-wallet is a new pm2 peer holding seed material sealed under an owner
passphrase on top of VAULT_STORE_KEY, with an unlock ttl after which the root key
is wiped from memory. five backends: on-chain via esplora, and lnd, clnrest,
lndhub and nwc for lightning. bolt11 encode/decode is implemented in-tree.

no secrets in the diff — the key-shaped literals under sidecar/wallet are the
bolt11 spec vectors and the bip39 "abandon … about" vector. .env.example gains
placeholders only. bun test src/servers/sidecar/wallet: 38 pass, 0 fail.

not reviewed line by line; assembled and verified to build, not audited.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 06:48:06 +00:00
pastilhasandClaude Opus 5 5ee56e736b add the invoices ui over the invoiceshelf sidecar
a /invoices workspace screen replicating the parts of the invoiceshelf web ui
that are actually used: dashboard, invoices, estimates, recurring invoices,
payments, expenses, customers, items and reports.

one document editor serves invoices, estimates and recurring invoices — they
share the line-item table, the discounts, the taxes and the totals, and differ
only in a header strip. the arithmetic is a port of upstream's
use-document-calculations, rounding points included, because the server
re-validates the totals it is sent. money is integer minor units throughout and
is converted to major units at the edges only.

selection and editor state live in the url (`/invoices/:section`, `?selected=`,
`?edit=`), so the nav highlight is derived rather than held, back closes an
editor instead of leaving the screen, and a half-written form survives a reload.

sending a document is behind an explicit confirmation — it emails the customer.

built against the running 2.4.2 instance rather than the 3.0 checkout in
_references; the two differ materially. typechecked, not yet exercised in a
browser.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 06:44:02 +00:00
pastilhasandClaude Opus 5 387664964c add a transmission sidecar and ui
officer-transmission is a new pm2 peer that owns the transmission rpc
connection and exposes a curated /_officer/* contract instead of proxying
raw rpc. it absorbs the three quirks callers otherwise have to know about:
the 409 x-transmission-session-id handshake, failures returned as
{"result": "..."} inside http 200, and basic auth where an empty username
must send no header at all.

/transmission is the ui, on the workspace/panel framework: a filter nav and
three sections (torrents, stats, settings). the torrent list is virtualised
with 30 available columns, multi-select, and a right-click menu; the detail
pane covers general, files as a real tree, peers and trackers. filters and
the open torrent live in the url, so a filtered view is a link.

phase 1 goal was parity with _references/transmission-web. follow-up work is
recorded in TODO.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 17:09:19 +00:00
pastilhasandClaude Opus 5 adf922de30 add the officer-headscale sidecar and its server registry ui
officer-headscale owns the whole Headscale contract: the registered servers and
their admin api keys, the >=0.29 version floor, and every multi-call composition
the ui needs. the platform side is auth+forward only and holds no headscale
credentials, so the existing /api/vpn/enroll route and its HEADSCALE_* env vars
are untouched and unrelated.

officer manages many servers rather than one. the owner registers each with a url
and a key generated on that server and switches between them; exactly one is
active, enforced by a partial unique index rather than by convention. keys are
encrypted at rest and never leave the sidecar — the list projection cannot return
one. registration validates before it saves: an unauthenticated GET /version to
prove something headscale-shaped is there and meets the floor, then an
authenticated call to prove the key works. an edit that moves either half
re-validates.

there is deliberately no transparent /api/v1/* passthrough. headscale serialises
every uint64 as a json string and its rest shape moved repeatedly below 0.29;
proxying raw would push all of that into the browser, which is the mistake the
soulseek panels made with 37 raw upstream calls.

the /headscale workspace is nav + view over the panel system. only the servers
section is implemented — nodes, users and pre-auth keys say so plainly rather
than rendering an empty table that reads as a failed fetch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 14:55:32 +00:00
pastilhasandClaude Opus 5 5a1e3d7e0b remove the projects feature
Being retired, so it is deleted rather than fixed — it had both defects dashboards just
had (edit dropping ?selected=, an abandoned edit following you onto the next item) and
repairing them was work for something on its way out.

Gone: the two screens and the panel apps, the three routes, the dock item and its entry
in the default dock paths (client and the three server-side copies), the page-title rule,
the app-registry entries, the barrel exports, the `projects` table with its queries and
row types, and the proj-meta/proj-layout/proj-terminals/proj-host-terminals branches in
the dashboards endpoint. The chat context and terminal state-key special cases for
`proj-layout-` went with them.

Deliberately kept: `getUserProjectsDir` in data-path.ts — the apps and dev-server routers
resolve user apps under the same on-disk Projects/ directory and are unrelated to this
feature. Nothing on disk is touched.

Needs `bun db:push` to drop the table; the schema change is the only thing standing
between the code and the database. One row was in it.

tsgo clean, 56/56 tests, no references left in src. Not yet exercised at runtime — the
restart is what will prove it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 08:16:27 +00:00
pastilhasandClaude Opus 4.8 2a509646e7 slskd: /soulseek search + downloads on the Workspace/Panel framework
Two panels (search + transfers) via WorkspaceView, coordinating over the
soulseek:refresh channel. Adds the page-title rule and documents the
route conventions in CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 17:21:29 +00:00
pastilhasandClaude Opus 4.8 35973a5505 activity: follow the agent's background work live + chat-event retention
"Activity" (placeholder name — jobs/tasks were taken) = watch background tasks
scroll in parallel with chat. Built DB-free; NOT restarted — deploy + test in
the morning.

- NDJSON progress contract (activity/progress.ts): capabilities append
  {job,cap,phase,status,pct,detail,ts,...} lines; tolerant parser treats any
  JSON object with phase/status as structured progress, else a raw log line.
- Backend (activity/router.ts, owner-only, path-guarded):
  - GET /api/activity/tasks — registry by scanning /tmp/claude-*/<cwd>/tasks/
    *.output (harness run_in_background) + announced detached jobs.
  - POST /api/activity/announce {name,path} — register a detached (setsid) job's
    log so it's followable too (the setsid case is on the critical path, since
    the warm worker now makes plain run_in_background the default for heavy jobs).
  - GET /api/activity/stream?task=<id>|path=<abs> — SSE tail (poll + offset),
    emitting {kind:'line'|'progress'} with NDJSON parsed.
- Frontend /activity screen + Radio nav item: task list (active dot) → live tail
  with a phase/pct progress header + raw log, following the /system-monitor pattern.
- Retention: startChatEventRetention() prunes chat_session_events >7d every 6h
  (wired in bootstrap) so the durable queue stays bounded.

Verified headlessly (no restart): parseTailLine classification, and the scan
finds 53 real task output files. Endpoints + UI untested until deploy.

Deferred (see handoff): cross-device sync + OpenCode parity (both touch the
now-stable chat path — won't ship un-restart-tested); task:progress into chat.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 00:56:10 +00:00
pastilhasandClaude Opus 4.8 d1e19d1473 system-monitor: /system-monitor route (CPU/mem/disk/processes) + page titles
- Backend GET /api/system-monitor/stats: one snapshot — CPU overall + per-core
  (two /proc/stat samples), memory + swap (/proc/meminfo), disks (df), load,
  uptime, top-20 processes (ps). Owner-only via the account gate.
- Frontend /system-monitor screen: polls every 2s; CPU/Memory/Disks cards with
  bars + per-core mini-bars, top-processes table. Nav dock "Monitor" item.
- Register /music and /system-monitor in usePageTitle RULES so the browser tab
  and editable header title update on those routes (/music was missing too).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 19:23:32 +00:00
pastilhasandClaude Opus 4.8 c3ed9158b3 music: /music route + nav dock item (Spotify-style page, v1)
A full-page music browser reusing the app-wide player + dock. Sidebar lists
libraries (1st level of ~/Music); main is a card-grid folder browse with rich
pages: albums show a header + tracklist, and artist folders render their album
cards grouped into discography sections (Studio Albums / Live / Compilation / …)
using /music/discography. Cards have hover-play; everything feeds useMusicPlayer.
Adds a green Music dock item (/music) to the default dock.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 12:49:05 +00:00
brunorezioandClaude Opus 5 1ac79f9c68 remove the onboarding flow and the accountMode leftover
Onboarding was dead in three layers:

- The OnboardingAdmin screen was only reachable from a route block in App.tsx
  that has been commented out, so it never rendered. Its ServerTypeCard carried
  accountMode ('organization' | 'single'), inherited from the codebase this was
  based on and meaningless for a single-user platform.
- Two /onboarding-complete endpoints, one public and one protected, that no
  frontend code called. Both read a server_config key that was never written, so
  both answered false while the app's own path defaulted to true.
- HomeScreen gated on settings.onboarding.complete to show a welcome panel, and
  seedHomeDir created an Onboarding folder from DATA_PATH/Onboarding and
  /Onboarding_Admin — neither seed directory exists, so it only ever produced an
  empty folder.

Also drops the onboarding key from UserSettings and the now-empty home-header
panel from the default home layout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 23:30:20 +01:00
brunorezioandClaude Opus 5 1fa3a659bc restore the code editor screen, drop the dead theme picker, unshadow AppRegistry
Three unrelated type errors that each pointed at something actually broken:

- Dashboard.CodeEditor was deleted in ab03b17 ("Projects") while the
  /code-editor route and the dock's Editor item kept pointing at it, so the
  route rendered undefined. Screen restored.

- Appearance.tsx imported 'themes', a workspace deleted in 0746844. Nothing
  reads settings.appearance.colorTheme and no theme CSS survives, so the picker
  was writing a value with no consumer. Removed it and the setting; colorMode
  stays, it is live.

- officerdev exported both a component and a type named AppRegistry, so
  `import { AppRegistry }` resolved to the type and <AppRegistry /> failed to
  typecheck. Renamed the Record type to AppRegistryMap.

Also declares "*.css" so side-effect stylesheet imports resolve.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 23:30:20 +01:00
pastilhasandClaude Opus 4.8 6f7bf8df42 remove the Automation page
Deletes the Automation and NewAutomation screens, their /automation and
/new-automation routes, the Dashboard barrel exports, the dock item (+ default
dock path), and the page-title rule. TaskRunnerModal is kept — the file browser
still uses it to run tasks on files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 14:39:09 +00:00
pastilhasandClaude Opus 4.6 5e38343f44 add pipeline job management with /jobs pages and per-step output viewer
- Pipeline jobs now persist to DB with progress tracking and cost accumulation
- Jobs survive WebSocket disconnects with in-memory event buffer replay
- New /jobs list page with search, status badges, and cost display
- New /jobs/:id detail page with live WebSocket attachment and REST fallback
- Two-column layout using WorkspaceLayout for resizable steps/output panels
- Streaming messages tagged with stepIndex/iterationLabel for per-step output grouping
- TaskRunnerModal links to job detail page once job is created
- Dock entry added for Jobs page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 08:48:50 +00:00
pastilhasandClaude Opus 4.6 40a9768cb3 claude-code streaming chat, desktop remote viewer, new-automation route, tiktok task v4, misc fixes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 20:00:45 +00:00
pastilhasandClaude Opus 4.6 927267e041 workspaces to dashboards, imap email sync, ffmpeg tool, tts fix, file browser refresh, automation sidebar reorder
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 20:00:45 +00:00
pastilhas bc4c20929c Discord 2026-02-27 08:27:43 +00:00
pastilhas e36908cb0b opengraph stuff 2026-02-24 21:47:36 +00:00
pastilhas ab03b175e7 Projects 2026-02-22 01:50:15 +00:00
pastilhas 6d97edb6ab cleanup 2026-02-21 15:36:07 +00:00
pastilhas 25f5f74b1b no idea 2026-02-20 18:25:33 +00:00
pastilhas 9870fa7ae8 /workspaces refactor 2026-02-19 16:07:00 +00:00
pastilhas 485ae4c3d7 Workspaces layout, automation page 2026-02-18 17:04:32 +00:00
pastilhas 5e2b2fad81 better panels 2026-02-18 11:33:30 +00:00
pastilhas 826904d74f Workspaces 2026-02-18 02:36:15 +00:00
pastilhas 030cf9fbd3 Code editor and widget 2026-02-17 23:48:46 +00:00
pastilhas 74e98e95e1 extracted chat component to widgets 2026-02-17 19:52:41 +00:00
pastilhas 21213c281d File browser as widget 2026-02-17 19:23:01 +00:00
pastilhas 0746844d6f extracted terminal to a widget 2026-02-17 18:46:18 +00:00