Commit Graph
32 Commits
Author SHA1 Message Date
pastilhasandClaude Opus 5 cd209483e3 fix the clipboard over http, and audit the rest
navigator.clipboard is secure-context only, like crypto.randomUUID before it —
over plain http on a tailnet address the object does not exist. Twenty call
sites across eighteen files, in three states that all looked fine in review:
bare calls that threw and killed the handler, optional-chained calls that
silently did nothing, and one carrying the comment "Officer is always behind
HTTPS", which it is not.

The optional-chained ones are the worst of the three: a copy button that reports
success and copies nothing is indistinguishable from a working one until someone
pastes.

helpers/clipboard.ts falls back to document.execCommand('copy') over an
off-screen textarea — deprecated, and it works on any origin because it predates
the secure-context rule. Off-screen rather than hidden, because display:none and
visibility:hidden elements cannot be selected and the copy fails silently.

Reading the clipboard has no equivalent: execCommand('paste') was never permitted
from script. The file browser's paste-a-file path now checks canReadClipboard()
and explains itself instead of throwing.

docs/http-secure-context-audit.md is the full sweep the owner asked for: what was
fixed, what cannot be, and what was checked and found clear. crypto.subtle is
used nowhere in the frontend, which was the one worth confirming since it has no
cheap fallback. Notification's six matches are type names, not the API.
geolocation and navigator.share are already guarded. getUserMedia is in four
files and is being removed — but QrTransfer uses it for the CAMERA, not a
microphone, so "remove audio" does not cover it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 03:56:57 +00:00
pastilhasandClaude Opus 5 6a74b3d198 name a chat from its own pane, and name the page after it
Two things, one title.

The pane's header is now editable and calls the same `renameSession` the list's pencil does, so both
surfaces write the transcript's `summary` line and the invalidation that follows refreshes the row. The
id comes from the URL rather than `resumeSessionId`: they agree for an ordinary conversation and not for
a merged `/clear` chain, where the resume target is the tail while the list and the server address the
chain by its head — renaming the tail would have written a title nothing displays. `/chat/new` has no
transcript yet, so there the title is read-only.

And on `/chat/<id>` the conversation names the page, sitting between a typed tab name and the route
default: `label ?? override ?? titleForPath()`. Naming a window is deliberate and must still win. Not
gated on full screen, though that is where it earns its keep — the nav header is hidden there, so the
browser tab strip is the only thing telling two side-by-side windows apart. Tiled, the same value fills
the header's centre.

The edit interaction is now one `EditableTitle` shared with the nav header instead of a second copy of
it. `allowEmpty` is what keeps the header's "clear it to hand the tab back to the route name" working;
everywhere else empty means keep, since the rename endpoint 400s on it. `SessionList`'s row rename is
deliberately NOT folded in — it opens from a pencil and confirms with a check, so it is a different
interaction wearing the same styling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 22:28:29 +01:00
pastilhasandClaude Opus 5 f3538cde25 delete the toaster that could never show a toast, and title the system settings page
`ui/toaster.tsx` was mounted in frontend.tsx and rendered a permanently empty
list: nothing anywhere imports `useToast`/`toast` from `ui/use-toast.ts`. The
app's real toaster is sonner, which is mounted beside it and has four callers.

`@radix-ui/react-toast` stays declared in the two package.json files on
purpose — installs are frozen, and dropping it means a deliberate
`bun install --no-frozen-lockfile` and a read of the lockfile diff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 12:58:21 +00:00
pastilhasandClaude Opus 5 667d622918 make the jobs back button a link, delete the backbutton nobody used
The audit said adopt the shared BackButton. It is not shared: zero importers
since the initial commit, no barrel entry, and a label-plus-underline shape
that fits none of the icon-only back controls here. Adopting it would have
redesigned the Jobs header under cover of a navigation fix.

So: a Link, matching what ScriptJobDetail and DownloadJobDetail already do,
and the dead component goes. useNavigate had no other caller in
PipelineJobDetail and goes with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 12:48:34 +00:00
pastilhas 32aa1e7cc3 delete the combobox instead of giving it anchor semantics
audit m7 ranked this medium because "every caller inherits the opaque click". there
are no callers. nothing has imported Combobox since the initial commit, there is no
barrel that re-exports it, and nothing anywhere sets `href` on a SelectOption — so the
navigate, the separator that only appeared for href options, and the href field on both
declarations of the type were all unreachable.

writing anchor semantics into a component that is never rendered is building, not
fixing. the Command primitives it used stay; AIHarnessesSection needs them.
2026-08-07 12:35:58 +00:00
pastilhas b419af32de sweep the dead code in section 8
Each item re-verified before deleting; three of the ten entries were stale
and are corrected in place rather than silently fixed.

- WorkspaceLayout's isMobile/mobilePanelId/onMobileBack: none of its ten
  callers set them, so the mobile collapse they fed was permanently off in
  that renderer. WorkspaceView passes the same props to WorkspaceRenderer
  itself, where they are live.
- fixedHeight on AppRegistryEntry, and getFixedHeight with it: no app has
  ever declared one, so it only contributed undefined. The flex-column
  branch it shared with fitContent stays, keyed on fitContent alone.
- getDefaults: getAllDashboardState already folds the defaults row into the
  one payload the client fetches, which is why it never got a caller.
- upsertScreen's terminals/hostTerminals: never read is right, never
  written was not — it inserted them, which is why all 15 rows hold {}.
  The columns are left in place; dropping them needs a db:push, and this
  tree holds another agent's uncommitted schema file.
- SELECTED_DASHBOARD_KEY: H2 (01365cb) replaced it with ?selected= four
  months ago and it has had no reader since.
- ui/sidebar.tsx and the stray ui/hooks/ beside it. use-mobile was not
  orphaned as claimed — the sidebar imported it — and the use-toast in
  there was a near-identical copy of the live one.
- findChildById's unreachable duplicate condition, and the doc comment that
  described the wrong behaviour rather than the code being wrong.

Left deliberately: DragOverlay/LayoutEditor (gated on 5.3, an owner
decision) and the two chat-owned channels, whose docs are fixed here even
though the publishers are not mine to delete.
2026-08-07 11:26:33 +00:00
pastilhasandClaude Opus 5 64961d49f5 catch render errors instead of showing a white screen
The repo had no error boundary anywhere, so a single malformed stored layout took the whole app down
and the only recovery was a psql session. Two boundaries, because "recover" means different things:

- around the routed screen in DashboardLayout, with the dock and header deliberately left outside so
  navigating away is itself a way out, plus a two-click reset of every `screens/*` layout for when it
  fails again in the same place. Dashboards are not touched — they are user-created and hold content.
- around each panel app in PanelSlot, so one bad app leaves the rest of the workspace running. Its
  recovery is "clear this panel", offered only when the layout is the user's to edit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 09:04:03 +00:00
pastilhasandClaude Opus 5 143a6453d3 carry the design language through gitea's remaining views
Commits, branches, cross-repo issues, notifications and organizations all move
onto DataRow, which is what makes them agree with each other — five lists that
were each hand-assembled from the same flex/gap/truncate parts now genuinely
share one row.

The repo header gets the same owner-muted / name-semibold split as the list row,
so the two screens agree about what a repository is called. Its description goes
from 12px to 14px, and the tab strip with it — those are read, not scanned, and
they were the smallest text on the busiest screen.

DataRow gains `href` for destinations outside the app. Notifications needed it:
Gitea's subject URL can usually be parsed back into an in-app route and
sometimes cannot, and the row should stay clickable either way rather than
becoming a dead div on the payloads that do not parse.

Empty states across the dashboard now say what the search actually covers.
"No open issues" was hiding that Gitea's cross-repo search only ever looks at
issues you created, are assigned, or are mentioned in — which is the difference
between a quiet week and a misconfigured token.

Typechecks clean and prettier is clean. Still not rendered in a browser.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 21:22:57 +00:00
pastilhasandClaude Opus 5 88652910d7 add the interface half of the duck suite design language
DuckSuite_Design_Language.md covers icons only — mascot geometry, lighting,
one-idea-per-icon, readable at 64px. It says nothing about type, density or
data, which is why the chrome looks considered and every data view does not.
This is the companion document plus the primitives that enforce it.

The diagnosis it is written against, from grepping two apps: 11 uses of
text-[10px], 7 of text-[11px], 52 of text-xs and 3 of text-base, with no rule
about which meant what; six radius values; hand-picked emerald/amber/red/purple
next to unused --success/--warning/--destructive tokens; text-black/50 and
ring-black/5, which are invisible in dark mode. None of that was a bad decision,
it was the absence of one thirty times over — so the fix is to remove the choice
rather than to have better taste.

docs/design-language-interface.md sets four type ranks with a 12px floor, one
focal point per row, five state tones, three radii and a spacing rhythm. The
principles are lifted from the icon language rather than invented, because
"one focal point, reads instantly, no unnecessary decorations, if it needs
explanation it is too complicated" is already the right rule for a dense list.

components/Data/ is how you spend that vocabulary: DataRow/DataList/RowMeta,
StatusPill/StatusIcon, LoadingBlock/ErrorBlock/EmptyBlock, RelativeTime. Rules
you have to remember are rules thirty views already broke, so the shape encodes
them — DataRow takes exactly one title and everything else is meta, RowMeta
puts separators between items so a trailing dot cannot appear, RelativeTime
carries the absolute timestamp as a hover title.

Adds --info (violet) as the fifth semantic tone, light and dark. "Merged" and
"in progress" are neither good news nor bad, and painting them with --success
makes a merged PR and an open one look like the same thing.

Entirely additive: nothing imports these yet, so no existing view changes and
there is no collision with the other agent working in this tree. Typechecks
clean. Not yet rendered in a browser.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 21:16:42 +00:00
pastilhasandClaude Opus 5 86d979046b restore list markers in rendered markdown
tailwind's preflight resets `list-style: none` on every ul/ol, and none of the
three prose scopes put it back. the indent was there, so a bulleted list just
looked tight — but an ORDERED list rendered with no numbers at all, which reads
as the model having emitted broken markdown. pasting the same text into an
editor showed it numbered correctly, which is the tell.

the `li::marker` rules were colouring a marker that was never drawn.

fixed in .chat-md, .file-viewer-md and .skill-md, plus the inline
.markdown-preview block in MarkdownEditor, which had the same hole. nested
levels follow the usual convention (disc/circle/square, decimal/alpha/roman) and
task-list items drop the bullet, since the checkbox is already the marker.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 03:00:16 +00:00
pastilhasandClaude Opus 4.7 e0bfc3d961 serve static assets from local public/ instead of static.officer.dev, simplify gmail history 404/410 handling
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 17:07:55 +00:00
pastilhas a93d3878f5 https://static.officer.dev 2026-02-24 21:59:53 +00:00
pastilhas ed0debfeac WorkspaceLayout in officerdev workspace 2026-02-22 22:43:28 +00:00
pastilhas 23b369c7e1 Workspaces 2026-02-22 00:05:29 +00:00
pastilhas 9e9acd9631 major apps and hooks refactor 2026-02-21 21:24:31 +00:00
pastilhas fe7874dd65 ready for app-registry refactor 2026-02-21 17:28:10 +00:00
pastilhas 25f5f74b1b no idea 2026-02-20 18:25:33 +00:00
pastilhas d7503ca56b Files FIles Files 2026-02-20 04:28:02 +00:00
pastilhas 4dde3aec66 workspace panel drag commented 2026-02-19 12:34:05 +00:00
pastilhas ebe2321f8f workspace panel drag 2026-02-19 12:31:30 +00:00
pastilhas 8b1bccab7a workspaces panel swap 2026-02-19 12:14:02 +00:00
pastilhas 111d4c86eb Hyprland 2026-02-19 12:03:45 +00:00
pastilhas dd8ab84df5 Workspaces in Workspaces all around 2026-02-19 01:49:15 +00:00
pastilhas 485ae4c3d7 Workspaces layout, automation page 2026-02-18 17:04:32 +00:00
pastilhas 89f23f9426 corrected folder upload and virtualization 2026-02-18 12:39:46 +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 c2660cd125 frontmatter in skills files 2026-02-18 01:23:22 +00:00
pastilhas 030cf9fbd3 Code editor and widget 2026-02-17 23:48:46 +00:00
pastilhas d32d0f5c03 refactored Authentication 2026-02-17 16:51:47 +00:00
pastilhas a1e416e894 theming 2026-02-17 01:19:29 +00:00
pastilhas 9ab0940ca4 first 2026-02-16 19:34:35 +00:00