Commit Graph

11 Commits

Author SHA1 Message Date
vh 6770ba26d6 feat(booth): kept boards — a .forever sentinel and a standing link board
Agent sessions hand the operator URLs and they drown in terminal
scrollback. The Booth is the right home for them — it already has the one
property that decides adoption, which is that a session can publish with
mkdir and cp, no API key, no schema, no deploy — but everything in it dies
in 24h.

So: a booth containing `.forever` is never swept, and renders in its own
Kept lane at the top of the index. Opt-in per booth, so the ephemeral
default is untouched and nobody inherits a cleanup chore. `rm` the
sentinel and the board rejoins the sweep; the CLI verbs are sugar over
exactly that, which keeps the filesystem-is-the-state model honest.

The pin is deliberately NOT wired into is_expired(). That stays a pure age
question feeding the `expires_in` countdown; only sweep_once() honours the
sentinel. Keeping expiry arithmetic and reaper policy apart means they
cannot drift into each other.

Kept cards are visually separated per Australis: a 2px top edge in aurora
blue, the one accent border the system sanctions. They show "kept" instead
of a countdown, and they deliberately lose the one-click wipe button — a ×
next to the durable stuff is a footgun, so removing a kept board is a
two-step act.

`booth link <url> [description]` appends to the standing `links` board,
creating and keeping it on first use. Entries carry provenance (handle or
hostname, plus a timestamp) because a bare URL is unreadable three days
later. The append is one printf of one line to an O_APPEND fd — atomic
under PIPE_BUF on POSIX — which matters because many agents post to one
board and interleaved half-lines would be the obvious failure mode.

Seven tests cover the sentinel: detection, survival of a sweep that wipes
its neighbour, the deliberate is_expired/sweep_once split, the listing
flag, the sentinel not inflating item counts, and both lane-rendering
directions. Two of them originally asserted on the bare strings "Kept" and
"kept-grid", which passed for the wrong reason — those also appear in the
inlined stylesheet served on every page — so they now assert the full
class attribute. 55 pass.

Also corrects the Homepage card's description, which advertised a flat 24h
TTL that is no longer the whole story.
2026-08-19 09:34:53 -07:00
vh 23cccf5f53 fix(homepage): force the canvas clear of the cached wallpaper div
Removing the `background:` block from settings.yaml was not sufficient.
Homepage server-renders the wallpaper as an INLINE style on
`<div id="background">` and Next.js caches the rendered page, so the aurora
survived both the config removal and a container restart. Only a full
recreate clears that cache, and recreating this container costs an hour of
missing tab bar and i18n before it heals itself.

Adding `#background` to the canvas reset is deterministic and immediate,
and it also keeps the canvas correct if the setting is ever re-added by
accident. The existing selector missed it: the DOM is
body > div#__next > div#background, so `body > div` matched the Next.js
root, not the wallpaper layer.

Verified live rather than locally: the served page now reports no
background image, with all three canonical faces loaded and the group
eyebrows rendering as JetBrains Mono in Australis cyan.
2026-08-19 09:17:37 -07:00
vh b271db1f44 feat(homepage): rebuild the theme on canonical Australis tokens
The predecessor theme was ugly for two structural reasons, not one.

It did not use the design system's colours. It built a parallel OKLCH
palette "derived from the Australis philosophy" and swapped the canonical
typeface for Supreme -- a fork, not a theme. Every hex here is now copied
verbatim from ~/.claude/skills/australis-design/colors_and_type.css, and
build.py re-checks all 19 against that file at build time and warns on
drift so it cannot quietly fork again. Type is the canonical stack: Space
Grotesk / Inter / JetBrains Mono, vendored as latin-subset VARIABLE woff2
(one file per family, 102 KB total against 56 KB for three static Supreme
cuts, and no Google Fonts request at page load).

It also carried a generated full-bleed aurora image behind the entire
dashboard. Canon forbids exactly that -- "solid fills only on chrome, no
full-bleed photography, no decorative gradients", and the aurora motif
"never as a background fill behind text". The predecessor knew, said so in
its own header, and dialled the opacity down rather than dropping it. The
image is gone; the aurora survives as a 1px accent edge under the tab bar,
which is where canon sanctions it. The asset stays in images/ in case it
is ever revisited.

Direction is instrument panel. Group headings become the Australis mono
eyebrow with a hairline to the right edge -- canon calls the eyebrow a
system signature, and it turns the groups into register bands instead of
headings floating over a grid. Status stops shouting: the filled emerald
chips read louder than the service names they annotated, so they are now a
semantic dot plus a mono micro-label at tertiary contrast. Cards are
bordered and opaque, per canon's border-over-shadow rule for chrome.

Alignment, per operator feedback that pills and cards did not line up:

- The status cluster is centred on the service name's line rather than
  parked in the card's top-right corner, where Homepage's `absolute top-0`
  left it floating ~7px above the title's optical centre. The offsets
  reconstruct the title line box and are documented as moving together.
- Descriptions get a two-line minimum, so the common one-line/two-line mix
  bottom-aligns across a row. This is what made the grid look ragged.
  useEqualHeights stays false: it inflated short cards to match a widget
  card twice their height, which was the worse failure.
- The status dot is flex-centred rather than nudged with vertical-align,
  so it stays centred if the type scale changes.

Retires the Skyfall sources and the Supreme faces; theme/ now has one
source of truth.
2026-08-19 09:14:13 -07:00
vh df68dd2753 style(homepage): tone the stat values down, run the aurora through the page
Two operator corrections in one pass.

Stat values overshot: the previous commit took them from font-thin 13px to
bold 22px in heading white, which went from whisper to shout. A stat only has
to out-rank its own label, not the service name above it — now --text-md at
medium weight in cyan, which clears the label but sits below the card title
where it belongs.

Colour lift, staying inside the system rather than around it: Skyfall names
Aurora (blue, cyan, green) the PRIMARY families, 'used generously, in that
order', while Dawn (amber, red, violet) is semantic-only. So group markers now
cycle blue -> cyan -> green down the page — icons at full strength, names at
0.72 — service icons take a single cool wash, header resource icons go cyan,
and latency tags move to the info family so 'how fast' stops looking like
'is it alive'. No Dawn colour is used decoratively anywhere.

Also fixes selectors that never bound: Homepage emits docker-status-<state>,
not status-<state>, so the green pills up to now were stock colouring rather
than this file. Both forms are matched and the trap is commented.

README records the iteration loop that would have caught the overshoot: CSS is
served per-request, so it needs a reload, not a recreate and not the layout
warm-up — and candidate CSS can be injected into the live page for a
seconds-long feedback loop instead of a 10-minute one.
2026-08-19 00:25:02 -07:00
vh f38cf69fe4 fix(homepage): invert the widget stat hierarchy — numbers lead, labels recede
Stock Homepage builds each stat as a font-thin (weight 100) 13px value above a
font-bold 12px uppercase label, so the number you actually came to read is the
quietest thing in the card while its label shouts. Skyfall's rule is that
hierarchy comes emphatically from weight AND size, and that numbers are data.

Value now renders at --text-xl bold in tabular mono at --text-heading; label
drops to a --text-2xs tracked eyebrow at --text-faint. The well itself moves to
--surface-input, one step DOWN from the card it sits on, so stats read as inset
data rather than as another floating surface — a recess, so it takes the
hairline without the shadow.

Also drops .service-block from the generic .service-tag rule, which was what
pinned every number to --text-2xs in the first place.

Visible on Plex, Jellyfin, PaperlessNGX and Uptime Kuma.
2026-08-19 00:07:20 -07:00
vh 45c1995d7a feat(homepage): Australis Skyfall theme + Arbo-generated aurora background
Replaces the previous theme attempt, which was built on a misread: the ask was
to use Arbo as an IMAGE-GEN ENGINE for the background, with the operator's
Australis Skyfall design system supplying the palette.

theme/ holds the source — colors/layout/typography vendored verbatim from the
Skyfall handoff bundle, Supreme 400/500/700 woff2, the Homepage bindings in
skyfall.css.in, and build.py which inlines fonts + tokens into
conf/custom.css. custom.css is GENERATED; edit the .in file and rebuild.

The build exists because Homepage serves only custom.css and custom.js out of
its config dir, so a @font-face pointing at a vendored woff2 would 404 — the
face has to arrive as a data: URI. The background image takes the other route:
/app/public/images is a real static route, so compose.yaml now mounts
images/ there read-only and settings.yaml points at /images/.

Bindings map Skyfall's semantic layer onto Homepage's DOM: Sea surfaces, the
depth recipe (hairline AND two-layer shadow, never one alone), uppercase
eyebrow group headers, the sanctioned accent-rail on the active tab rather
than a glow, and semantic status colour so a green pill means the service is
actually serving.

Background generated by Arbo (irv-ml1:8201) workflow t2i-ui-background, job
13f0891f4e42, seed 26, flux2-klein-9b, 2048x1152 — abstract, no subject,
cool-temperature aurora. 1.6 MB PNG -> 22 KB WebP.

Two deviations are documented rather than hidden: Skyfall forbids imagery
behind body text (held at opacity 30 as mitigation), and service icons stay
full-colour vendor logos.

NOT DEPLOYED — live still runs the old theme. Prototype on :5199.
2026-08-18 23:30:27 -07:00
vh c3de7dbd58 feat(homepage): add Arbo 'Raven' theme to custom.css; correct the tab-bar note
Ports Arbo's design tokens (irv-ml1:8201) into conf/custom.css — flat raven
ink #021425, card surface #112333 on #1B2E3D borders, Manrope, and mint
#2FFC89 reserved for signal so a green pill means the service is actually
serving. Values read off Arbo's running :root custom properties rather than
sampled from a screenshot. CSS rather than settings.yaml because Homepage's
color: setting only accepts built-in Tailwind ramps.

NOT YET DEPLOYED — live still runs the stock theme pending an A/B decision.
Prototype is at 10.0.50.45:5199; shots in ~/booth-data/homepage-cleanup/.
Promoting it also means dropping the background: block from settings.yaml.

Also corrects the previous commit's tab-bar claim. It is not a fixed few
minutes of warm-up: a fresh container was still tab-less at 4m30s twice, and
recovered on its own about an hour later. Cause remains unpinned; the README
now records the measured timing and the four ruled-out causes.
2026-08-18 22:38:21 -07:00
vh 42c594c29f fix(searxng,seafile): repair wget healthcheck argv, restore seafile after 3-month outage
searxng: the healthcheck passed '--tries' and '--spider' as separate argv
entries, so wget consumed '--spider' as the value of '--tries'. Spider mode
never engaged and every 30s probe downloaded the response to disk; the
container's working directory had accumulated 295,287 healthz.N files since
April, and the directory scan to pick the next free filename is what
intermittently blew the 10s timeout and flapped the dashboard card to
UNHEALTHY. Restored '--tries=1'. The junk was in the writable layer, so the
recreate cleared it. Now healthy, fails=0, 200 in 0.16s.

seafile: none of the three services declared a restart policy, so Docker
defaulted them to 'no'. The daemon stopped all three within 200ms on
2026-05-06 and nothing brought them back — a three-month outage whose only
trace was an EXITED card. Exit 255 is what a container ignoring SIGTERM
reports when the daemon stops it, not a crash. Added restart: unless-stopped.
Stack is back up; mysql gates on its healthcheck as designed and seahub
started without the race. 302 -> login page.

Both stacks were running unmanaged on ana-docker and are now tracked here.

homepage: AI tab reordered by clickability per operator — chat frontends,
ComfyUI and the control plane on top; vLLM /docs seats and TTS endpoints
below. Corrects the previous commit's UNRESOLVED tab-bar section: it was
warm-up time after a recreate, not a defect.
2026-08-18 22:27:15 -07:00
vh 9d92c4bd21 fix(homepage): pin UltraSeedbox to one tab, dedupe Uptime Kuma, size columns to members
UltraSeedbox had no layout: entry, and Homepage renders an untabbed group on
every tab — eight full-width bookmark bars repeated four times. Pinned to Main
with a row layout.

Uptime Kuma rendered twice: a manual services.yaml block under Monitoring plus
homepage.group=Apps on the container. Dropped the manual block, moved the
label to Monitoring, added homepage.siteMonitor. Adopted the previously
unmanaged uptimekuma stack into stacks/ so the label is version-controlled.

Column counts declared more columns than groups had members, leaving the last
row of several groups mostly empty. Columns now track member counts.

Also records an UNRESOLVED regression: since the container was recreated the
client render has lost its tab bar, wallpaper and i18n. Ruled out the config
changes (committed pre-cleanup config reproduces it) and v2.0.0 (v1.13.2
reproduces it). Server HTML still carries the tab markup, so the loss is
client-side. Details in the stack README.
2026-08-18 19:12:02 -07:00
vh d1f4f1cb96 docs(homepage): record the ALLOWED_HOSTS fix, .env lockdown, and verification method
HOMEPAGE_ALLOWED_HOSTS now carries the IP:port form; direct access to
http://10.0.50.45:5100/ returns 200 and the host-validation errors are gone
from the container log.

The .env was mode 644 holding the Plex and Jellyfin API keys; now 600. It is
root-owned, so editing it needs the infra-ops identity -- lkraven has only
password-sudo on that host.

Also records that Homepage renders client-side, so grepping the served HTML
to verify a config change is the wrong instrument (it gave a stale prerender
and then an empty page). GET /api/services is the honest check, and config
changes need a recreate rather than a restart.
2026-08-17 21:14:22 -07:00
vh c5beeac32d feat(homepage): bring the fleet dashboard under version control
Homepage on esh-docker-vm:5100 was the one stack whose config lived only on
the host, edited in place. Its version history was six hand-rolled
services.yaml.bak-* files. Now canonical here and deployed with
deploy-stack.sh like everything else; the .bak files are gone.

Corrections from the audit:
- ANA-Firewall described a 'Fortigate 81F'. It is a FortiGate-80F running
  FortiOS 7.2.10, verified live against the device.
- NH3-Ansible pointed at 10.100.50.42 as an 'Ansible control node'. That host
  is nh3-extdev, the manager/external-dev successor after nh3-ansible was
  retired. Renamed and re-described.
- Dropped the UltraSeedbox layout group: nothing provides it, so it only ever
  rendered empty.

Adds .env.example and a README documenting the two-path service model (docker
label discovery across five engines vs manual entries), the labels-only-apply-
on-recreate rule, and the foot-guns found: HOMEPAGE_ALLOWED_HOSTS matches
host AND port so a bare IP does not cover IP:port; :2375 is plaintext and
unauthenticated on all five engines; ping: cards can only be judged from the
dashboard host.

Verified after deploy via /api/services: 105 cards across 19 groups, both
corrections live, ana-docker discovery intact.
2026-08-17 21:10:37 -07:00