Commit Graph
59 Commits
Author SHA1 Message Date
valknar e63129d156 Add triggershell service logs command
CI / Checks (push) Failing after 48s
CI / Publish to npm registry (push) Skipped
Thin wrapper around journalctl, consistent with the existing status/uninstall wrappers around systemctl.
2026-08-19 17:58:41 +02:00
valknarandClaude Sonnet 5 96a66fc857 Add structured backend logging with pino
CI / Checks (push) Successful in 47s
CI / Publish to npm registry (push) Successful in 50s
Wires leveled, structured logging (pretty in dev, JSON in prod) through
the server lifecycle, HTTP/WS request handling, run engine, auth, db,
and config loading. CLI command output is left untouched since it's
user-facing terminal UX, not backend logs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v1.0.4
2026-08-19 09:54:16 +02:00
valknar 3272b9db76 fix: actually stage the checks/publish split
CI / Checks (push) Successful in 48s
CI / Publish to npm registry (push) Skipped
The previous commit renamed release.yaml to ci.yml but never staged
the content edit underneath it (git mv picked up the last-staged
version, not the unstaged working-tree changes) - it pushed with the
filename changed but the workflow itself untouched. This is the
content that commit was supposed to carry.
2026-08-17 17:38:31 +02:00
valknar 06edc60b55 ci: run checks on every push, publish only on tag
Same split as pulsenode's workflow: previously this only ran at all
when pushing a version tag, so lint/typecheck/format/test never ran on
regular commits or PRs - a broken push could sit unnoticed until
someone tried to cut a release. Now checks run on every push and PR;
publish to the npm registry stays gated to a tag push and requires
checks to pass first. Renamed release.yaml -> ci.yml to match.
2026-08-17 17:37:01 +02:00
valknarandClaude Sonnet 5 14158047ac Make the README link in docs/API.md absolute
Release / release (push) Successful in 1m6s
The docs viewer serves docs/*.md at /docs/<slug> but doesn't ship
README.md as a route, so the relative ../README.md link resolved to
a dead /README.md path in the rendered web UI. Point it at the file's
Gitea URL instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v1.0.3
2026-08-17 10:01:00 +02:00
valknarandClaude Sonnet 5 e5fb28ef31 Widen the doc detail view to max-w-5xl
Matches the width already used on the run and new-run cards.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 09:57:19 +02:00
valknarandClaude Sonnet 5 e7b2b9add2 Fix docs pages overflowing horizontally on mobile
Release / release (push) Successful in 1m36s
GFM tables (config reference's Field/Type/Default/Notes tables) are
wider than a phone screen and don't wrap, so without their own scroll
container they forced the whole page to scroll horizontally instead.
Wrap rendered tables in an overflow-x-auto div, and let long unbroken
strings in table cells and inline code (env var names, paths) break
instead of forcing extra width.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v1.0.2
2026-08-17 09:23:31 +02:00
valknarandClaude Sonnet 5 5574ffd1c8 Add a minimal 404 page matching the app's style
Two boundaries share the same content: src/app/not-found.tsx catches
genuinely unmatched URLs (rendered bare in the root layout), and
(app)/not-found.tsx catches notFound() calls from within app routes
(already used by scripts/[scriptId] and runs/[runId]) so it renders
nested inside AppLayout, keeping nav and footer visible.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 03:12:19 +02:00
valknarandClaude Sonnet 5 adeb21be19 Fix Re-run button squeezing the run metadata row in the header
CardAction's default row-span-2 reserved header column 2 across both
the title row and the metadata dl row below it, shrinking the dl's
available width (cramping the Run ID column) to fit around the
button. Scope the button to just the title row and let the dl span
the full header width on its own row.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 03:05:55 +02:00
valknarandClaude Sonnet 5 335e7624b4 Quote variable values with spaces in the displayed run command line
Values containing spaces (e.g. an env-passed SCENE="Glitz and glam")
rendered as bare, space-separated words in the run detail view,
indistinguishable from separate argv/env entries. Now anything outside
a safe bareword character set is quoted and escaped for display only -
actual execution is unaffected, since values are always passed as
discrete argv elements/env vars, never through a shell.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 03:01:53 +02:00
valknarandClaude Sonnet 5 23a4e2ebc0 Widen run and new-run cards to max-w-5xl with a multi-column form layout
The narrower max-w-2xl card left a lot of unused width on scripts with
several variables, forcing a long single-column scroll. Widening the
card and laying out variable inputs in a responsive grid (up to 3
columns) uses that space; textareas and checkbox groups still span the
full width since they don't shrink well into a column.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 20:37:11 +02:00
valknar b77041cfa8 Size the run terminal by aspect ratio instead of viewport height
h-[60vh] made the terminal's height track the viewport regardless of
its actual width, so it read as too tall on narrower layouts.
aspect-video keeps it at 16/9 relative to its own width instead.
2026-08-16 18:15:17 +02:00
valknar e482810328 Show env-passed variables in the displayed run command line
Release / release (push) Successful in 1m6s
redactedCommandLine only ever included argv (script.command + args),
so a passAs:env variable like a scene name was invisible in run
history even though it's the main thing that varied between runs.
Secrets still redact to *** instead of being omitted outright.
v1.0.1
2026-08-16 17:31:31 +02:00
valknar 86aa0b7539 Fix prettier formatting on the new combobox control
Release / release (push) Successful in 1m5s
2026-08-16 17:23:30 +02:00
valknar c7bc4421c5 Add a searchable combobox control for enum variables
select/radio don't scale to enums with dozens of choices. Reuses the
same cmdk Command/Popover primitives multi-select already uses, just
single-valued instead of an array.
2026-08-16 17:21:11 +02:00
valknarandClaude Sonnet 5 a496dc4865 Drop the redundant explicit build step from the release workflow
`pnpm run build` and pnpm publish's automatic prepack hook
(rm -rf .next && next build && rm -rf .next/cache) both ran a full
next build - the explicit step's output got thrown away and rebuilt
from scratch seconds later inside publish anyway. Kept only prepack's
build, since it's the one that actually has to succeed for a
publishable package to exist; a deterministic build that just passed
isn't going to fail differently a few steps later.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 14:24:11 +02:00
valknarandClaude Sonnet 5 677aabfa30 Exclude .pnpm-store from prettier and git
Release / release (push) Successful in 1m25s
On the Gitea runner, pnpm's content-addressable store ends up inside
the workspace (.pnpm-store/) instead of the global cache location -
format:check was scanning its content-addressable blobs as if they
were source files, some of which happen to parse as JS/TS-like text
and crash prettier's parser outright rather than just wasting time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v1.0.0
2026-08-16 14:19:44 +02:00
valknarandClaude Sonnet 5 8ca8c57793 Stop routing pnpm install through the Gitea registry
Release / release (push) Canceled after 1m42s
actions/setup-node's registry-url sets the *default* npm registry for
every install, not just publishing - since triggershell is an
unscoped package name, that meant `pnpm install` tried to fetch every
ordinary dependency (zod, typescript, ws, ...) from
dev.pivoine.art/api/packages/valknar/npm/ instead of the public npm
registry, and got hammered with 429s retrying each one.

Removes registry-url from setup-node entirely (installs go back to
the default public registry) and instead scopes the auth token to
just that one registry host+path via `pnpm config set
"//dev.pivoine.art/api/packages/valknar/npm/:_authToken" ...` right
before the publish step - publishConfig.registry in package.json
already tells `pnpm publish` specifically where to go (verified
locally via `pnpm publish --dry-run` earlier), this only supplies the
matching credential without touching install resolution.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 14:16:41 +02:00
valknarandClaude Sonnet 5 3f391ff584 Drop pnpm cache from the release workflow - not reachable on this runner
actions/setup-node's cache: pnpm option tries to hit this Gitea
instance's Actions cache service, which times out (ETIMEDOUT against
an internal address) rather than failing fast - burning ~5 minutes on
every run before falling back to an uncached install anyway. Not
worth it for a release workflow that runs once per tag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 14:13:46 +02:00
valknarandClaude Sonnet 5 ef453eadd0 Bump release workflow's Node to 22 - pnpm 11 needs it to even run
Release / release (push) Canceled after 8m18s
pnpm 11.21.0 (pinned in packageManager) now uses node:sqlite
internally, which requires Node >=22.13 - unrelated to this project's
own engines.node: >=20 floor for end users. With node-version: 20 the
runner's pnpm binary couldn't execute at all (ERR_UNKNOWN_BUILTIN_MODULE
on the first pnpm invocation inside actions/setup-node's cache-path
detection), before ever reaching the actual lint/build/publish steps.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 14:05:48 +02:00
valknarandClaude Sonnet 5 a11dfd8f7e Rename the release workflow's secret to PACKAGE_TOKEN
Release / release (push) Failing after 1m14s
Gitea rejects secret names starting with GIT (not just the GITEA_/
GITHUB_ prefixes), so GITEA_PACKAGE_TOKEN wasn't a valid name.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 14:01:40 +02:00
valknarandClaude Sonnet 5 2a5b0b98f4 Add a Gitea Actions release workflow: lint/typecheck/format check -> build -> publish
Triggered on tags matching v*.*.* - runs the quality gate (lint,
typecheck, format:check, the existing test suite, build) as
individual steps for clear failure attribution, then publishes to
this Gitea instance's own npm registry (dev.pivoine.art, unscoped
package name - Gitea's npm registry supports that directly, no
@owner/ rename needed).

The release version comes from the git tag (v1.0.0 -> 1.0.0 via
`npm pkg set`), patched into package.json only in the CI run, never
committed back. publishConfig.registry in package.json is a static
string (safe to commit); the auth token is supplied at publish time
via NODE_AUTH_TOKEN, written to a CI-generated user-level .npmrc by
actions/setup-node's registry-url option rather than a repo-committed
one - pnpm >=10.34.2/11.5.3 (this repo pins 11.21.0) blocks ${VAR}
expansion in repository-controlled npmrc/pnpm-workspace.yaml
specifically to stop a malicious repo from exfiltrating CI secrets
that way, so the token can't live in a committed .npmrc at all.

Verified locally end-to-end short of the actual registry upload:
lint/typecheck/format:check/test/build all pass, and
`pnpm publish --dry-run --no-git-checks` after a temporary version
bump confirms publishConfig.registry resolves to the right URL and
prepack (next build) fires automatically as part of publish.

One-time manual setup this can't do by itself (documented in the plan
file): a repo-scoped Gitea Personal Access Token with the `package`
Read&Write scope, stored as the GITEA_PACKAGE_TOKEN repo secret -
Gitea's own auto-injected GITEA_TOKEN explicitly cannot publish
packages (unimplemented per Gitea's own docs).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 13:55:47 +02:00
valknarandClaude Sonnet 5 c6337ea942 Run prettier across the repo, exclude the lockfile from it
Prettier had never been run in --check mode here before, so this had
drifted across most files (markdown tables, long option() chains,
line wrapping). Purely formatting, no logic changes - needed so a CI
format:check gate can actually pass. Adds .prettierignore for
pnpm-lock.yaml specifically: prettier's YAML formatter rewrites every
quoted key (single -> double quotes) producing an ~8700-line diff of
pure noise on a file pnpm itself owns the formatting of.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 13:55:25 +02:00
valknarandClaude Sonnet 5 e13b7e3b1a Add triggershell run/scripts - execute configured scripts from the CLI
`run <scriptId>` auto-detects whether the web server is already
reachable (a quick /api/healthz check):

- If it is, the run goes through the existing POST
  /api/scripts/:id/runs endpoint (token-authenticated, same as any
  other API client) and the CLI subscribes over /ws/runs exactly like
  a browser tab - so the run shows up live in Run History and any open
  browser watching it, with zero server-side changes, since the
  broadcast path has no idea a run was triggered by a click vs a CLI
  invocation.
- If nothing's reachable, it calls startRun() directly in its own
  process (after its own migrateOnBoot/reconcileOrphanedRuns, so a
  from-scratch .triggershell/ works standalone) and streams output by
  listening on the same in-process runEvents emitter a WS client would
  otherwise be fed from - read-log-then-listen, the same ordering
  ws/server.ts's subscribe() already uses, so a fast script finishing
  before the listener attaches still gets its output printed.

Both modes support --var name=value (repeatable; repeat a name for
multiselect), --no-wait, and Ctrl-C cancellation through the same
mechanism the web UI's Cancel button uses (a WS cancel message
remotely, cancelRun() directly locally). `scripts list`/`scripts show`
are local-only, no network - same direct-config-read pattern as
`validate`/`doctor`.

Extracts defaultValuesForScript() out of dynamic-form.tsx into
src/lib/config/defaults.ts so the CLI's --var handling and the web
form fill in a script's configured defaults identically instead of
duplicating that logic.

Verified live end-to-end: a CLI-triggered remote run was observed
streaming to both the triggering CLI process and an independent WS
client (simulating a browser tab) simultaneously; local-mode Ctrl-C
confirmed to actually kill the spawned child process, not just the
CLI; token, wrong-token, and TRIGGERSHELL_API_TOKEN auth paths all
verified against a running auth-enabled server.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 12:29:55 +02:00
valknarandClaude Sonnet 5 28407402c8 Make the favicon track the header icon's primary color per theme
The nav's Terminal icon uses text-primary, which resolves to a
different brass shade in light vs dark mode - the favicon was
hardcoded to only the dark-mode shade. Uses prefers-color-scheme
so the favicon glyph always matches whichever primary the header
is actually showing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 11:49:41 +02:00
valknarandClaude Sonnet 5 7e1569b94c Restyle the UI: instrument-panel palette, type system, and a signal accent
Replaces the default shadcn grayscale/Geist look with a deliberate
identity built around the product's own subject - a control panel for
running scripts and watching their output live:

- Type: Bricolage Grotesque for page/card titles (via the existing
  --font-heading token, used with restraint), IBM Plex Sans for UI body
  text, IBM Plex Mono for technical data (run IDs, commands, timestamps,
  status labels) - all self-hosted at build time via next/font/google,
  no runtime CDN dependency for a self-hosted tool.
- Color: a cool graphite ink/paper base with a warm brass signal accent
  in both themes. The brass tone doubles as the "running" status color,
  so an active run literally lights the UI up with the brand color.
  New --status-* tokens give queued/running/succeeded/failed/cancelled/
  warn a single source of truth instead of ad-hoc Tailwind color classes.
- Structural language: small tracked-out uppercase mono labels mark
  technical fields (Command, Variables, Triggered by...) consistently;
  the live-output terminal gets an instrument-bezel frame (header bar +
  panel) instead of floating loose above the xterm canvas.

Layout/IA is unchanged throughout - this is a token- and detail-level
pass, not a restructuring.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 11:44:30 +02:00
valknarandClaude Sonnet 5 f102ace7e8 Drop the stale sharp entry from pnpm-workspace.yaml's allowBuilds
pnpm re-scaffolds this block from the current dependency tree when it
finds unapproved build scripts; sharp is no longer a candidate, so
pnpm install can't fill it in and errors out with ERR_PNPM_IGNORED_BUILDS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 11:25:07 +02:00
valknarandClaude Sonnet 5 3f379ca2ac Flatten the repo: move everything out of app/ to the root
Now that the CLI and the Next.js app are one package, nesting it inside
app/ served no purpose - the repo root itself becomes the published
npm package. Merges app/.gitignore and app/README.md into the root
versions, drops the now-duplicate app/LICENSE, and updates path
references (README, docs/ARCHITECTURE.md, docs/CONFIG_REFERENCE.md,
package.json's repository.directory) that assumed the app/ nesting.

Also fixes a real bug this surfaced: the in-app docs viewer resolved
docs/ relative to process.cwd(), which only worked by accident when the
CLI happened to be invoked from app/'s parent directory. A first attempt
at fixing it with import.meta.dirname broke instead, for the same
cross-module-graph reason config-path resolution already documented -
Next compiles Route Handlers through a separate module graph that
doesn't preserve source-relative import.meta paths. Fixed by exposing
the app root via TRIGGERSHELL_APP_ROOT (set once in server.ts, where
import.meta *does* resolve correctly), the same pattern already used
for TRIGGERSHELL_CONFIG_PATH.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 11:14:01 +02:00
valknarandClaude Sonnet 5 30350d80f4 Replace the Python CLI with a Node CLI, add a systemd service command
The app is already 100% Node, so the Python launcher was pure overhead - it
existed mainly to bootstrap Node, which is circular. The CLI is now merged
into app/ (the single published npm package): `triggershell start` validates
the config and imports server.ts directly in-process, so server.ts's own
SIGTERM/SIGINT handling just works with no signal-relay/child-process layer
needed. `dev` is dropped from the public CLI (contributors use `pnpm --dir
app dev` directly); there's no `build` command either, since the package
ships a prebuilt `.next` via a `prepack` hook. Adds `triggershell service
install|uninstall|status` for running as a per-user or system systemd unit.

Also fixes two bugs found while wiring this up: server.ts resolved `.next`
relative to `process.cwd()`, which broke once the CLI could run from a
directory other than the app itself; and an explicitly-`files`-listed
package directory bypasses .npmignore for its subpaths, so `.next/cache`
was inflating the npm tarball to ~670MB (now stripped in `prepack`, ~7MB).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 11:03:25 +02:00
valknar e2b6c6102c chore: remove architecture doc in web app 2026-08-16 06:09:31 +02:00
valknarandClaude Sonnet 5 042391cdc3 Match run detail card width to the new-run form card
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 06:08:40 +02:00
valknarandClaude Sonnet 5 dc34b38b26 Drop "contributors" from the footer copyright line
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 00:59:11 +02:00
valknarandClaude Sonnet 5 346bf7995a Style inline code in docs as a pill instead of literal backticks
Tailwind Typography's default inline <code> style is just bold text
with decorative backtick characters added via CSS content - with docs
like CONFIG_REFERENCE.md that use backticks constantly, it read as
unstyled/half-parsed. Swapped it for the same muted rounded-pill look
already used for inline code elsewhere in the app (e.g. the dashboard's
"no scripts configured" message).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 00:58:14 +02:00
valknarandClaude Sonnet 5 1b415a7957 Add a footer and an in-app docs viewer
Footer (in the authenticated app layout, matching where Nav lives):
copyright line, a Docs link, and a link to the project repo.

Docs viewer: /docs lists docs/API.md, CONFIG_REFERENCE.md, and
ARCHITECTURE.md; /docs/[slug] renders one via react-markdown +
remark-gfm (tables, fenced code) inside a Tailwind Typography `prose`
block, dark-mode aware via prose-invert. The markdown files themselves
stay the single source of truth at the repo's docs/ - the app reads
them at request time rather than duplicating their content, resolved
from the app's cwd the same way config paths already are, since a
compiled Route Handler's module graph doesn't preserve source-relative
paths.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 00:54:15 +02:00
valknarandClaude Sonnet 5 d7c0be6b22 Add a Re-run button to the run detail page
Re-run takes you to the script's form pre-filled with that run's
variable values, rather than re-executing immediately - this is
necessary, not just cautious: stored run.variables already have secret
fields redacted to "***" (see the run detail command/variables view),
so a true one-click re-run would either fail validation or, worse,
silently pass the literal string "***" to the script as a real secret.
Pre-filling instead lets the user review/tweak values and forces secret
fields to be re-entered, both of which are new safe defaults now that
required fields are actually enforced.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 00:32:30 +02:00
valknarandClaude Sonnet 5 75b92d103c Fix status/script filter dropdowns being cramped on mobile
SelectTrigger defaults to w-fit, and the toolbar only set a width from
sm: up, so below that breakpoint each dropdown shrank to its label text
instead of using the full row it was already stacked onto.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 00:24:18 +02:00
valknarandClaude Sonnet 5 3f656baad1 Make Duration sortable on the runs page
It was left out because duration isn't a stored column, just
started_at/ended_at math - now expressed as a SQL case expression so it
can be sorted the same way as the other columns. Still-running rows
sort by elapsed-so-far; never-started (queued) rows sort as NULL, which
puts them out of the way at whichever end matches the current
direction.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 00:20:06 +02:00
valknarandClaude Sonnet 5 3131d8ce5b Add search, filtering, sorting, and pagination to Run History
The runs page was a flat top-100 list with no way to narrow it down.
It's now driven entirely by the URL (?q=&status=&scriptId=&sort=&dir=&page=),
so filtered/sorted views are shareable and survive back/forward
navigation:

- free-text search across script name, triggered-by, and the resolved
  command
- status and script dropdown filters
- sortable Script/Status/Triggered-by/Started column headers
- offset pagination (25/page) with a real total count, clamped so an
  out-of-range page falls back to the last valid one instead of
  showing a misleading "no results"

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 00:17:49 +02:00
valknarandClaude Sonnet 5 d4e4c2c7d2 Show the full resolved command and variables on the run detail page
The command lived in a narrow truncate'd grid cell, so anything but a
short invocation was unreadable. It now gets its own scrollable code
block, plus a breakdown of each variable (using the script's configured
labels when available) and the value it resolved to for that run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 00:11:27 +02:00
valknarandClaude Sonnet 5 004586ee64 Enforce required on string/multiselect variables without explicit bounds
required:true only worked by accident: a bare z.string() or
z.array(...) accepts "" / [] just fine, so a required field with no
minLength (e.g. secret tokens like webhookToken, apiKey) could be
submitted empty and the run would start anyway. Required now implies
min(1) when no stricter bound is already configured, for both string
and multiselect fields. This is the single schema shared by the client
form resolver and the server run-creation route, so both now reject it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 00:06:39 +02:00
valknarandClaude Sonnet 5 e6008a22cd Fix live output missing for fast-finishing scripts
Scripts that finish in milliseconds (e.g. notify.js) could complete and
broadcast all their output/status before a client's WS subscribe message
even arrived, leaving the run page's terminal permanently blank with no
way to catch up. The server now answers every subscribe with whatever
log bytes were written past what the client's server-rendered page
already had, plus the run's current status, before it starts streaming
live broadcasts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 00:02:57 +02:00
valknarandClaude Sonnet 5 c3312f5dc6 Remove theme toggle from the login page
Keep it in the main app header only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 21:17:49 +02:00
valknarandClaude Sonnet 5 79a7c8f5cb Tighten xterm terminal line height
1.4 was too loose for a log/terminal view; 1.2 stays readable while
fitting more output on screen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 21:14:11 +02:00
valknarandClaude Sonnet 5 a4db53f44d Add dark theme support with a toggle
shadcn had already generated full light/dark CSS variable sets in
globals.css (keyed off a .dark class via @custom-variant), but nothing
ever added that class - the app was always light regardless of OS
preference. Wires up next-themes (already a dependency, pulled in by
sonner.tsx but never provided) with attribute="class", defaulting to
the system preference. Adds a sun/moon ThemeToggle button - in the
header for the authenticated app, and in the top-right corner of the
login page since that route sits outside the header layout.

suppressHydrationWarning moves to <html> too (next-themes sets the
class there via a pre-hydration script, so server/client will
legitimately differ on first paint).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 21:11:27 +02:00
valknarandClaude Sonnet 5 35b0d95792 Remove unused create-next-app placeholder assets
file.svg, globe.svg, next.svg, vercel.svg, and window.svg were only
ever referenced by the scaffolded demo homepage, which is long gone.
Nothing else in the app references them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 21:02:26 +02:00
valknarandClaude Sonnet 5 e779c5a93b Use the header's Terminal icon as the app favicon
Replaces the default create-next-app favicon.ico with an icon.svg
matching lucide-react's Terminal glyph (same one used in the header)
exactly, on a dark rounded-square backdrop so it stays legible at
16x16 in the browser tab regardless of the OS/browser chrome theme.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 21:01:13 +02:00
valknarandClaude Sonnet 5 acc6715a88 Allow timeoutSeconds: 0 to mean no timeout
Previously every script was forced to have a positive timeout capped
at 24h, with no way to run something genuinely unbounded. execa only
enforces its timeout option when it's greater than 0, so this just
relaxes the schema's lower bound from positive to >= 0 and lets that
flow through unchanged - no engine logic needed beyond a clarifying
comment. The 1800s default and 86400s cap for finite timeouts are
unchanged; 0 is an explicit opt-in, not the default.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:57:24 +02:00
valknarandClaude Sonnet 5 97bb6cc092 Add per-page titles
Root layout now defines a title template (%s · TriggerShell) so every
page just sets its own short title instead of repeating the brand
name. Static titles for the dashboard, login, and run history pages;
dynamic generateMetadata for the script and run detail pages, since
those need the script/run name which isn't known until the route
params resolve.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:52:11 +02:00
valknarandClaude Sonnet 5 7daaa95ab9 Hide the Next.js dev-mode route indicator badge
Sets devIndicators: false in next.config.ts. Compile/runtime error
overlays are unaffected - only the floating route-info badge is hidden.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:45:46 +02:00
valknarandClaude Sonnet 5 6d7788e48b Keep the TriggerShell wordmark visible on mobile
Only the icon-only nav links, username, and logout label needed to
collapse to fit a phone-width header - the brand text alone doesn't
push it over, so keep it always shown for identity.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:40:37 +02:00