Skip to content

Releases: eengelking/podium

v1.3.0

Choose a tag to compare

@eengelking eengelking released this 22 Oct 20:27
1edc179

Added

  • The reporter helper forwards only reporter routes, and no regex can be made to backtrack on a long input (#668, part of #577). The helper that carries the mod's calls over mutual TLS now answers 403 to any path or method that is not one of the reporter calls, and builds the request it sends from the matched route. Twelve regexes that could take quadratic time on a long run of slashes or blanks (a trailing-slash trim on a request path took 3.4 s on 100,000 slashes) are replaced with string handling, and the four confirmation buttons on Admin > Certificates and People take focus in code instead of with autoFocus.

  • Failed requests are limited per address (#667, part of #577). Ahead of every /api route, one address may make 300 requests a minute that end in a response of 400 or more. The next one gets 429 with Retry-After until the minute is over, and the first refusal in a window is logged as auth.throttled. Successful requests are never counted, and PODIUM_FAILURE_LIMIT changes the limit. It is a flood brake on the paths that check a token or a certificate, which CodeQL reported as unlimited (js/missing-rate-limiting); the wrong-secret lockouts are unchanged. New dependency: express-rate-limit.

  • A built-in TLS guide: browser trust, the reporter, expiry and the threat model (#590, part of #577). docs/SERVER.md has a new "Built-in TLS" section that walks podiumctl start --tls, the two ports, that no browser needs the CA, the reporter config, the HTTP switch, what to do when a person's certificate, the serving certificate or the CA expires, and what TLS stops and does not stop. docs/REPORTER.md has a table of what each reporter status-line message means, and the User guide's "Something is wrong" table points to both. Uploading your own certificate is not supported. The guide was walked on a throwaway server with Claude Code sessions in tmux, which found three reporter defects, filed as #645, #646 and #647. docs/DECISIONS.md 42 no longer says nothing is built, and docs/SERVER.md says a wildcard bind adds no address to the serving certificate.

  • The Certificates page says why Rotate CA is off for a CA made before rotation with an overlap (#638, part of #577). OpenSSL refuses the chain [leaf, cross] under a CA with pathlen:0, which is what the first TLS builds made. For such a CA the confirmation now explains that it cannot be rotated with an overlap, disables Rotate CA with that reason beside it, and leaves Rotate CA and end the overlap now, which replaces it with a CA that can take a planned rotation next time. GET /api/admin/certificates has a new canOverlap field.

  • Admins can stop reporters from connecting over HTTP, with a warning while they can (#589, part of #577). Admin > Server > Certificates has a new section, Reporters over HTTP, with the switch Allow reporters to connect over HTTP (tls.allowReportersOverHttp). It is on for a server that already had reporters when TLS was first turned on and off for a fresh one, and a warning line shows in both states. Turning it on is immediate. Turning it off asks first, names how many reporters are connected over HTTP now, and says the console on loopback keeps working. With it off, the HTTP listener answers 403 to the routes a reporter calls (/api/fleet/report, /hook, and the peers and respond routes) from any address but loopback, logs tls.http_refused, and shows the source in the Reporters page's refused list; a reporter that has a config uses HTTPS and is unaffected, and the console is never gated. A change is written to the Admin log as tls_http_changed. podiumctl and the Containerfile already publish both ports (#580).

  • Rotate the CA with a cross-signed overlap, and retire the old one (#588, part of #577). On Admin > Server > Certificates, Rotate CA… asks first and then makes a new CA, signs a cross certificate for it with the old one (120 days), and serves [new leaf, cross certificate], so a reporter that holds only the old CA still verifies the server. Both CAs are trusted for client certificates. A reporter that renews during the overlap gets the new CA and the cross certificate in the renewal response (rotation) and adds the CA to its config file only if the cross certificate verifies under a CA it already holds and the new certificate under the new one; it never replaces a CA. The page shows a blue "rotation in progress" band and a Previous certificate authority row with "N of M reporters still use it", and Retire old CA… names that count, refuses those reporters at once and closes the connections that came in on the old CA. Rotate CA and end the overlap now is the hard cutover after a leaked key: no overlap, nothing delivered, every reporter needs a reissued config, and every open connection with a client certificate is closed. The listener, the serving renewer and the certificate check now read one list of trusted CAs, which closes the T5 review's concern that a certificate could pass TLS and be refused for its issuer. A new CA has pathlen:1; a CA made earlier (pathlen:0) can only rotate by ending the overlap. New routes POST /api/admin/certificates/rotate-ca and /retire-ca; GET /api/admin/certificates adds rotation and overlapDays. Mockup: mockup/podium-tls-certificates.html (#573), state "CA rotating".

  • Admin > People shows each credential, one Actions menu per row, and the reporter config after Create user (#587, part of #577). The table has a Reporter certificate cell and a Console token cell, each with its own badge and a note that opens on hover and on focus (the date and what happens next for the certificate, pdm_<key id> for the token); the Status column is gone, the Group select sits under the name and "created" under Last report. Every row has one Actions menu, grouped Console token, Reporter certificate and Person, with Up, Down, Home, End and Escape; a rotate, a revoke, Revoke certificate, Revoke all and Reissue config ask first in a full-width row under the person. After Create user, Reissue config or a token rotation the show-once box gives the token and the file reporter-config-<name>-<epoch>.pem with Download reporter config, once; Download certificate gives the public certificate and the CA, never a key. GET /api/admin/people adds tls, graceDays and each row's certificate (notAfter, revoked). With TLS off the certificate cell says so and the certificate actions are disabled. Revoke token still revokes the person's certificate too (SPEC 7.7), so its confirmation says the reporter is refused; the mockup's "Their reporter keeps working" for it is not followed.
    Standards: the row Actions menu (.menu, .menubox, .mi, .mgh, .msep), the confirmation in a full-width row under a table row (tr.confirmrow), the show-once box's second part (.once h3 and ol.plain), and the People table's two credential cells with the Group select under the name; approved with the mockup on #573 (decisions 11, 15 to 19, 22 and 27).

  • Admin > Server > Certificates and the certificate pill in the header (#586, part of #577). The new page shows the CA and the serving certificate with their dates and a status that is a shape, a word and a color, a band at the top (blue, orange or red as the first of them runs out), the reason a renewal failed in a critical row, the CA fingerprint with a copy control and Download CA, and the serving certificate's names in a small dialog. The serving certificate's lifetime and renew-before days, a person's certificate lifetime and grace days save when the field is left, with a refused value shown in place, and the header countdown has its own switch. Renew now works from the page, the certificates Podium kept are listed, and an expired CA lists the steps to restore access. The header shows a pill after the connection pill, blue at 30 days or fewer, orange at 10, red at 5, and "Certificate expired", for whichever of the CA and the serving certificate expires first. It links to the page and has no X; the switch hides only the countdown, and a failed renewal or an expired certificate always shows. It reads the new public GET /api/tls/status, and GET /api/admin/certificates now also returns the CA, the header setting and a person's certificate lifetime and grace (reporter). Rotate CA... is on the page but disabled until the rotation lands (T10); People, the HTTP switch and the rotation state follow in T9 to T11. Mockup: mockup/podium-tls-certificates.html (#573).
    Standards: header status pills .pill.soon (blue) and .pill.serious (orange), and a status pill that is a link (a.pill); Admin table badges .badge.soon, .badge.serious and .badge.bad with an icon inside a badge; the red band row .band.crit and the band row's text layout on an Admin page (.band .bt, .band .bd); .nset and .note for a grouped list and a quiet note in a small modal; a focus ring on the tooltip trigger (.tip:focus-visible); and td.nw and the CA fingerprint line's wrap. Orange text uses the existing band color (--serious mixed with --ink), since no --serious-text token exists. Approved with the mockup on #573 and in the decisions comment on #323 (#323 (comment)).

  • A person's certificate can be renewed once an hour (#612, part of #577). POST /api/reporter/renew answers 429 with Retry-After and the code TOO_SOON while the person's newest live certificate was issued less than an hour ago, before it starts openssl or adds a row, so a looping or hostile client holding a valid certificate cannot fill person_certificates or keep the server busy. The limit is fixed at an hour, not a setting. The helper treats a 429 as a wait, not a refusal: the request still goes out, the file is left alone, and it asks again after the Retry-After time (never sooner than a minu...

Read more

v1.2.2

Choose a tag to compare

@eengelking eengelking released this 20 Oct 03:02
95a6250

Fixed

  • SonarCloud's main branch is scanned after each release (#510). The Sonar workflow ran on pushes to development and on PRs only, so SonarCloud's default branch, main, kept the analysis of the 1.1.0 release from 2026-10-17 and showed none of 1.2.0 or 1.2.1. A push to main now runs the scan. CI configuration only; no change to the product.

v1.2.1

Choose a tag to compare

@eengelking eengelking released this 20 Oct 02:41
6ee08d3

Fixed

  • Board columns reach the footer in wall mode (#497). The columns stopped at a fixed 640 px height and left a gap above the legend. In wall mode they now take all the height between the Board header and the legend, follow the window as it is resized or goes fullscreen, and scroll inside themselves when they hold more cards than fit. With the "no open steps" strip showing, the columns end above it. Outside wall mode the Board is unchanged.
  • The rail no longer pages one orchestrator at a time on a narrow window (#474). Below 1040 px the panes stack and the rail's height follows its rows, so it measured only the room its rows already took, paged, shrank, and settled on one row a page ("Showing 1–1 of 5"). A stacked rail now lists every row with no pager, and widening past 1040 px pages again on the page that holds the selection. Wide windows page as before.
  • The Sonar scan no longer stalls for five minutes (#501). Every full analysis spent 300 s in SonarJasmin's React render simulation, which starts at frontend/src/main.tsx and walks the whole component tree until it hits its time limit; the job took 6 to 8 minutes, which looked like a hang during the 1.2.0 release. main.tsx, a 30-line bootstrap with no logic or tests, is now excluded from the scan. CI configuration only; no change to the product.
  • podium_send can reply when several orchestrators share a name (#500). podium_peers now shows a short session id for orchestrators that share a name and machine, and says only name@machine#id reaches them. podium_send accepts that form and delivers to exactly that session; a bare duplicated name@machine is still refused, now with the ids to use instead of asking the person to rename one. An inbound message carries the sender's id, so the mod's reply line names name@machine#id. An older server sends no id, and the mod then falls back to the plain address.
  • The history paging test no longer counts a timer's reads (#494). "builds rows for the requested page only" spied on every getAgents call, so a session broadcast timer that fired during a slow request (under coverage in CI) added to the count. The test now holds the throttle open for its fixture, so only the request's own calls are counted. It still fails if the route builds every row. Test only; no change to the product.

v1.2.0

Choose a tag to compare

@eengelking eengelking released this 19 Oct 23:44
f6b9a51

Changed

  • The Plan list puts finished steps at the bottom (#454). On the orchestrator page the Plan tab lists the step in progress first, then blocked steps, then the ones still to do in plan order, then the done steps in the order they finished. The open work stays on the first page of a long plan. A step the model reopens goes back among the open ones. The server numbers the finishes, so a reload keeps the order; steps already done when a plan first arrives come first among the done, in plan order. A step that moves keeps its row and your page, and a screen reader hears one polite line. The count, the bar and the Board keep plan order. See SPEC 8.1 and DECISIONS 38.

Fixed

  • My reporters counts a request's age by the server's clock (#467). "Asked 4 min ago" and "Proposed …" under Waiting for you used the browser's clock, while Your reporters used the server's, so a browser whose clock was off showed a request asked seconds ago as minutes old or in the future. They now use the server's time from the read, as Traffic does. The copied call(), DROP_WORDS and Blank on the messaging pages are one each, with no change to what the pages show or to the log's wording.
  • Admin and History draw the same top bar as every other page, and a page's own button is left off it (#468, #470). Admin had its own header with only "Back to fleet"; it now shows Fleet, Board, History, Traffic and the sign-in controls like the rest, and the locked, disabled and checking screens keep it. History was missing the Traffic button while orchestrator messaging is on. The Admin button no longer shows on Admin, the Traffic button on Traffic, or the My reporters link on My reporters, each page's pill already naming it. See SPEC 8.1.
    Standards: the top bar leaves off the button for the page you are on, now including Admin, Traffic and My reporters (approved by the maintainer on #468/#470); StreamTopBar is the shared bar for pages without a wall.
  • The Markdown "stays linear" test no longer times out in the Sonar scan job (#471). Its four heaviest cases built a node per marker at 50,000 repeats and took about 4.5 s each, so a slow coverage runner could pass the 20 s limit. N is now 15,000 and the backstop 60 s; the coverage run of the file drops from about 27 s to 9 s, and a quadratic renderer still fails every case it affects.
  • The Admin "changes the rows per page and returns to page one" test no longer times out on a slow runner (#477). It paged through 125 people with no limit of its own and took about 5.7 s on CI against the 5 s default. It now uses 60 people, enough for a second page and a smaller page size, and carries the same 60 s limit as the paging test above it; it still fails if Rows per page stops returning to page one. No other Admin test takes more than about 350 ms locally.
  • Plan finish order survives a report with no plan, and a replaced plan starts over (#465). A plan that went missing for a report and came back unchanged lost every finish number and fell back to plan order; the server now keeps the last plan across the gap. A new plan that reused the old step ids (TaskCreate numbers from 1) inherited the old numbers; plans now count as the same only with equal source, title and step texts, and any other starts over. Database migration 21 adds plan_kept_json. See SPEC 8.1 and DECISIONS 38.
  • SonarCloud reliability findings that failed the 1.2.0 release gate (#487). The standards scale reader and the doc-link checker's heading and link patterns no longer backtrack on long input, the Resume and Fork popup cells on the orchestrator page are a named group, and the Board's group list is sorted with a compare function. That last one changes the order of group names that differ in case: "alpha" now sorts before "Zeta". The group cells' markup gained role="group" and an aria-label, so the Standards components page was regenerated; nothing looks different.
  • The doc-link checker strips HTML tags from a heading until the text is stable (#489). This answers CodeQL's js/incomplete-multi-character-sanitization alert; the anchors it computes are unchanged. CodeQL's js/file-access-to-http alert on scripts/podium-peers-seed.mjs is a false positive (the only file data in a request is the repo's own package.json version, sent to the --url given), left for the maintainer to dismiss.
  • The diff redaction test builds its AWS temporary key id, Google key and Stripe webhook secret samples at runtime (#488), so GitHub secret scanning no longer flags test data. The redactor is checked on the same text as before.

Added

  • Title wall cards with the machine (#480). Admin > Settings > The wall has Title cards with the machine, off by default. On, each card on the wall is titled machine | session, such as Silverbolt | Foobar (the pipe set apart in the muted color), for everyone on this server and without a restart; off, titles are the session's name as before. A session with no known machine name keeps its session name, and one with no name shows its machine's. The rail, the Board and the orchestrator page are unchanged. The change is written to the Admin log. See SPEC 8.1.
  • Orchestrator messaging in Admin (#416, C5 of #347).
    • Settings. Admin > Settings > Feature flags has Orchestrator messaging (peer-messaging, off by default). While it is on, an Orchestrator messaging section sets the round trips before a person approves (10), messages per minute on a link (6), the longest message (20,000 characters) and how many days a conversation is kept (30). A value saves when its box loses focus or on Enter. One out of range is refused in place, and the error says nothing was saved and which value still applies.
    • A reporter's Messaging section. Admin > Reporters > a reporter shows the owner's reporters with the same Grid | Board as My reporters. A change is a proposal that shows as Proposed, waiting for the owner, until the owner accepts it. Only more than a pair has can be proposed: lowering a right or taking it away is the owner's choice. The server holds that line too: a proposal at or below the stored level is refused (409), and accepting a proposal, like allowing an access request, never lowers a right that rose since. Pause all links in the header applies at once, and Resume all links undoes it. The details add System and Messaging. A reporter on the admin secret, with no owner, with messaging off on the machine, or too old to message says so and offers no choices.
    • One read. The Admin's GET /api/admin/reporters/:key/peers now covers every pair of the owner's reporters and returns the owner's trust, so the Grid needs one read.
    • See SPEC 7.1, 7.7 and 8.1, and docs/SERVER.md.
    • Standards: a new shared component, the limit rows (components/PeerLimits.tsx: .srow rows with an in-place p.err refusal, a state line and a Use the Admin's limit button), for Admin > Settings and My reporters > Defaults. Approved with the #347 mockups (issuecomment-6257162320) and the #436 mockup, accepted by the maintainer on 2026-10-19 (#436 (comment)).
    • Standards: a new section, the Admin's Messaging section on a reporter (admin/ReporterMessaging.tsx), with Pause all links / Resume all links in the detail's header and a .warnbar for proposals and for the owner's Trust. Approved with the #347 mockup (issuecomment-6257162320); its cases with no design were accepted by the maintainer on 2026-10-19.
  • An owner's stricter limits on My reporters (#436, C7 of #347). My reporters > Defaults shows the three messaging limits: round trips before you approve, messages per minute on a link, and the longest message. Each box shows the limit that applies, and its most is the Admin's value. Use the Admin's limit, or typing the Admin's value, goes back to it, even when your own value is the same as the Admin's or above it. A value above the Admin's, below 1, not a whole number, or an empty box is refused in place, and nothing is saved. If the Admin later lowers theirs below yours, yours is kept and the row says the Admin's applies. See SPEC 8.1, docs/USER_GUIDE.md and mockup/podium-my-reporters.html.
  • Traffic (#417, C6 of #347). A console page at #/traffic, reached from Traffic in the top bar after History while orchestrator messaging is on.
    • The map. Inline SVG: a box per machine with its OS, each orchestrator with its status shape and state word, and a line per conversation. A line's state is its dash pattern plus an icon and a word on its label with the message count. An arrow shows a one-way link, and a thicker line means more messages. There is a legend.
    • The conversation. Selecting a line (click, or Enter on the focused line) opens the conversation beside the map. It has Pause link or Resume link, Remove link… with a confirmation, the round trips used against the limit, and the messages as an Activity feed with each one's delivery state. It also says how many days a conversation is kept, from a new retainDays field on GET /api/fleet/peers/traffic, which also gains serverTime (the Table counts "ago" by the server's clock) and a paused flag per conversation (a paused link with a message waiting for approval still reads as paused).
    • Table and Show. A Table view (where a narrow screen opens) and Show: Live sessions, Last 24 hours, Last 30 days.
    • Who reads what. Signed out, or not the owner, the map and counts show and the page never asks for the text.
    • scripts/podium-peers-seed.mjs --talk seeds conversations to look at. See SPEC 8.1, docs/USER_GUIDE.md and mockup/podium-traffic.html.
    • Standards: a new component, the Traffic map (components/TrafficMap.tsx; .pm-map, .pm-mapscroll, .pm-left, .pm-key-ink, .pm-key-warn, .pm-key-muted): machine boxes, nodes with StateIcon, lines as links with lab...
Read more

v1.1.0

Choose a tag to compare

@eengelking eengelking released this 17 Oct 04:32
20d7185

Added

  • A favicon. The browser tab shows a white agent-tree mark on a dark tile, so the Podium tab is easy to pick out among many open tabs. It is a single SVG, frontend/public/favicon.svg, served from the app's own origin.

  • Podium warns when responding would send its sign-in unencrypted (#306). With Respond from Podium on and the server bound beyond loopback, the log gets one server.respond_plain_http warning at start and another each time the flag is turned on; a sign-in to respond that arrives over plain HTTP at a name that is not loopback is noted at most once a minute, whatever the bind address. Admin > Settings adds a caution to the Respond from Podium row when the console is loaded over http: from a non-loopback host, and the reporter mod starts its status line with ⚠ and toasts once per session when its server URL is http:// to another machine. Nothing is blocked, and no token, header or cookie value is logged. Until the server can trust a proxy's X-Forwarded-Proto (#305), a TLS proxy in front is not recognised from the bind address, so ignore the start warning behind one. SPEC 7.7, SECURITY.md and the user guide say so.

  • A CI check fails a PR into main that is not a release or a hotfix. The "PR target check" workflow passes only when the PR's head is development or hotfix/* in this repo, and fails with a message that says to change the base to development. main is the GitHub default branch, so a contributor's "New pull request" proposes it by default. CI and documentation only; no behavior changes.

  • The Admin log is pruned (#303). Nothing ever removed a row from admin_log, so a busy fleet with responding on added one for every answer for as long as the database lived. The hourly retention run now deletes rows older than 90 days, set with RETAIN_ADMIN_LOG_MS (also accepted by podiumctl config set), and always keeps the newest 100 rows so a quiet server keeps some history. GET /api/admin/log returns the period as retainMs and Admin > Admin log says how long entries are kept. There is no Settings control for it yet. SPEC 7.2, the user guide, the README and SECURITY.md say so.

  • The Admin log says what became of an answer, not only that the server took it (#304). respond_sent now ends with the delivery's short id ([dlv_1a2b3c4d]). When the mod reports a result, a respond_result row follows with the same actor and id: the session took it, queued it, did not take it, refused it, or it had closed before the session got it. When the server drops an answer before the session has it, a respond_dropped row says so: its sender discarded it, or its ask closed first. Neither row, nor the server log's respond.result and respond.dropped, carries the text of an answer. SPEC 7.7 and the user guide say so.

Fixed

  • A queued message that is lost when its session ends is written to the Admin log (#334). A message the session had queued showed respond_result ("the session queued it") and then nothing if the session ended before Claude Code read it. It now gets a respond_dropped row saying the session ended while it was still queued; the row does not say it never arrived, because Podium cannot know. A message that later got took writes no extra row. SPEC 7.7, the user guide and DECISIONS 34 say so.

  • Deliveries dropped when a session is deleted or responding is turned off are written to the Admin log (#333). Deleting a session, or turning off Respond from Podium or the activity feed, discarded every waiting answer, message and command with no row. Each one that had no result yet now gets a respond_dropped row, one per delivery and not one for the lot, with the sender as actor and the same short id as its respond_sent row, saying the session was deleted first or responding was turned off first. A session that ended and was then deleted writes one row per delivery, not two. SPEC 7.7, the user guide and DECISIONS 34 say so.

  • A retried final delivery result answers 404, and that is now the contract (#329). POST .../deliveries/:id/result for a message with a final result, or an answer the session refused, finds nothing the server still holds and answers 404 unknown delivery. The bundled mod drops a queued result on any response, so it does not retry a 404; DECISIONS 34 records why the server keeps no finished-delivery record, and tests lock the route and the mod. No behavior changed. SPEC 6.2 says so.

  • A message dropped when its session ends is written to the Admin log (#326). A message or /clear or /compact command that was accepted and still waiting for the mod when the session ended vanished without a trace, so the log showed respond_sent and nothing after it. Each one now gets a respond_dropped row with the same actor and short delivery id, saying the session ended first, before any result came back; the server log's respond.dropped has why: session-ended. A message the session already took writes no extra row; one it queued is followed up by #334, below. No row carries the text. SPEC 7.7 and the user guide say so.

  • A retried delivery result no longer logs respond.delivered twice (#330). #327 stopped the second respond_result row, but the server log still got a second respond.delivered line for the same result. RespondStore.result() now says whether a result was new, a repeat or for a delivery the server no longer holds, and the route logs only a new one. What the route answers does not change.

  • A retried delivery result no longer writes a second respond_result row (#327). A mod that posted the same result twice for one delivery, as after a network timeout, left two identical rows in the Admin log. A repeat of the result a delivery already has is now ignored; a different result (queued, then took) still writes its own row. The route still answers 200 while the server holds the delivery; a message with a final result, or a refused answer, is no longer held, so a repeat of that one is a 404 as before and writes no row. SPEC 7.7 says so.

  • One machine reports one machine id, however the mod is loaded (#267). The mod kept its random machine id in its own store, one file per plugin load, so a copy loaded inline and the marketplace install each had an id and one machine showed as two rows on Admin > Reporters. The id now lives in podium-machine-id in the Claude config directory (CLAUDE_CONFIG_DIR, else ~/.claude) and every load reads it. A load with no file adopts the id its store already holds, so a machine with one install keeps its row; a machine that had two ids settles on one, and the other row stays live only while its session does. Where the file cannot be read or written the store is used alone, as before. SPEC 6.2, DECISIONS 21, GOTCHAS and the README say so.

  • The start-on-boot unit no longer names a node that brew upgrade deletes (#268). podiumctl enable wrote the path of the running node, and Homebrew's is Cellar/<formula>/<version>/bin/node, a directory the next upgrade removes, so the unit would fail at the next boot. It now writes opt/<formula>/bin/node when that leads to the same binary. On Linux, podiumctl disable no longer says the server is stopped only in standalone mode. The unit has now run on Bazzite in both modes; DECISIONS 11 and GOTCHAS say what that covered and that a reboot is still untested.

  • The notes on Admin > Reporters and the agent panel's Kind row show on keyboard focus (#275). They used title plus tabIndex, which a browser shows on hover only, and the Kind row's span had no role. One Tip component now shows its note on hover and on focus, closes it on blur, mouse leave and Escape, wraps in a narrow panel and is placed from the trigger so a table's scroll area cannot clip it, and keeps the text in the page as a role="tooltip" line the trigger is described by, so a screen reader reads it either way. It replaces the Reporters state, admin secret and "no name set" notes and the Kind row's data-tip styling. The wording of the notes is unchanged. ARCHITECTURE says so.

  • "Lock Admin" stays locked after a reload (#272). POST /api/admin/unlock/from-respond let an Administrator's console sign-in reopen Admin at once, so locking Admin on purpose, for example before leaving a shared screen, lasted only until the page was reloaded. Lock Admin now marks that browser's sign-in as held: a reload shows the name-and-token sign-in, while the console sign-in itself stands and the person can still answer sessions. Unlocking with the name and token of the person signed in to respond there lifts the hold, a new sign-in to respond starts without one, and another browser's sign-in is unaffected. The 10-minute idle lock still lets a signed-in Administrator back in. The Admin sign-in screen no longer says Admin opens without it for anyone signed in to respond; it now mentions Lock Admin. DECISIONS 30, SPEC 7.7, ARCHITECTURE and the user guide say so.

  • "Est. cost today" counts a session that started before midnight and now reports no ledger cost (#286). The mod sends no ledger cost on a local model, so a session that spent on a Claude model earlier in the day and then switched counted $0 today, in the total and in the Spend tab. It now counts what it costs now minus what the usage on its last sample before midnight cost, priced with the Admin's prices, so only its spend after midnight is added; the owner split still adds up to the total. A session none of whose samples kept usage (a reporter older than 0.12.0 on a local model) still counts nothing. DECISIONS 23 and SPEC (KPIs) no longer list this as a gap.

Changed

  • The three groups in Admin's side menu read as separate sections (#244). Each group's heading is now 18 px (16 px from 761 to 1280 px of viewport, where the narrower rail would wrap it) in the primary ink with a small outline icon and a rule under it, and the groups are 24 px apart,...
Read more

v1.0.2

Choose a tag to compare

@eengelking eengelking released this 16 Oct 23:38
4b21eca

Fixed

  • The prompt and answer modal renders a fence inside a fence and a link with parentheses in its URL (#274). A fenced block now closes only on a line of backticks at least as long as the one that opened it, so a shorter fence quoted inside a longer one stays text and the block renders as one. A link URL holding one level of balanced parentheses, such as https://en.wikipedia.org/wiki/A_(b), now links the whole address instead of stopping at the first ). Only http(s) and mailto links open, as before.
  • The cost-per-minute sparkline follows the Admin's model prices (#270). It was drawn from Claude Code's ledger samples, so after a price change it disagreed with the cards, the facts bar and "Est. cost today". Each sample now keeps the session's per-model usage (migration 16; in full only when it changes or at a minute's first sample, so a quiet session adds one character per second), and the series is priced from it like the other figures, so a price change moves the sparkline at once. Samples from a reporter older than 0.12.0, or from before this release, keep the ledger's figures, and the step where the two kinds meet counts as zero. Existing samples have no usage, so the sparkline's last hour mixes both for an hour after the upgrade.
  • A cost that is not known reads as a dash, and priced spend stays visible next to unpriced spend (#276). Checked against the code after #117: a loop that has not reported a model yet and has spent nothing showed $0.00 (hook-only agents always did); it now shows a dash. The "not counting N unpriced sessions" note counted every session on a non-Claude model, even after the Admin priced it; it now counts only sessions whose cost is still unknown. The note replaced the cost-per-minute sparkline on the Est. cost today tile; the sparkline now stays beside it. A session first heard of after midnight, whose start the server believed from just before it, counted $0 today; it now counts what it spent since its first sample. Already right and left alone: a session that switches from Claude to a local model keeps its Claude spend on its card and in the total when it started today (#117). Still open, noted in DECISIONS 23: a session that started before midnight and now reports no ledger cost counts nothing today.
  • A fleet card, a rail item and the wall now say "delegating" when the orchestrator page does. A session whose orchestrator is running while an agent it launched runs used to read "Running" on its card and show the green dot in the left rail, while its page said "Delegating to 1 agent." The card's band now reads "Delegating to N agents." in the blue style (hover for the agents), the rail item shows the blue hollow dot and reads "delegating" to screen readers, and all three take the state from the same function as the page, so they cannot disagree. They return to Running when the last agent finishes. It is still display-only: the session counts as running in the status filter, the sort and the counts. UI only. (#287)

Changed

  • A documentation-only or chore PR is no longer a release. CLAUDE.md and CONTRIBUTING.md now say that from 1.0.0 on only a PR that changes code or behavior carries a release. A PR that changes only documentation, comments, CI or chores bumps no version, adds its entry under [Unreleased] and gets no tag. The bump is chosen by what the PR changes, not by its issue's label, and the maintainer may bundle several issues into one release: each PR adds its entry under [Unreleased], and a final release PR cuts the version once they have all merged. This replaces the 1.0.0 rule that made documentation-only PRs patch releases.
  • The fleet store no longer keeps lastMessageAt (#278). Nothing read it since #184 removed the "synced" counter. The field, its test assertion and the argument that fed it are gone; no behavior changes.
  • The plan panel's empty state uses the shared sentence (#278). It read "No plan reported. Agents were launched ad hoc, so only the agent tree is available."; it now reads "No plan reported. Agents were launched ad hoc.", as the card and the focus header do and as SPEC 7.x says. The user guide is updated.
  • CLAUDE.md describes a second, opt-in branching workflow. A new "Branching Model" section says that when the repo's GitHub default branch is development, feature PRs are squash-merged into it, main moves only on a release PR from development (merged with a merge commit), the version bump belongs to that release PR, and hotfixes branch from main. With the default branch on main, nothing changes. The Do Not list, "Always Branch First", the workflow table (a new hotfix row) and the Working row name development where they name main. The Releases section still describes per-PR releases and would need adapting if this repo opts in. Documentation only; no behavior changes.
  • Dependencies. CI uses actions/checkout 7 and actions/setup-node 7 (#8, #9). The dev group moves supertest to 7.3.1, @types/supertest to 7.2.1, tsx to 4.23.15 and @testing-library/react to 16.3.3 (#10). dotenv moves from 16.6.1 to 18.0.7 (#11); since dotenv 17 prints an "injected env" line at startup, which the logger's text output does not expect, the server now loads .env with quiet: true. React 19 (#12) and TypeScript 7 (#13) are not taken: each needs its own migration.

v1.0.1

Choose a tag to compare

@eengelking eengelking released this 16 Oct 22:27
71739ef

Changed

  • The README opens with the problem Podium solves and a logo. The lead-in now describes a session waiting on you with no way to tell which one, and presents Podium as a single pane of glass for one person or a whole team. The feature description became a short list under the screenshot, and "Run it" is now "Quick start". Adds docs/images/podium-logo.png. Documentation only; no behavior changes.

v1.0.0

Choose a tag to compare

@eengelking eengelking released this 16 Oct 19:13
5e26814

This is the first stable release. From here on every PR to main is its own release (see #241 below).

Changed

  • From 1.0.0 on, every PR to main is a release (#241). CLAUDE.md now says that a breaking change is a major release, a feature a minor release, and a fix, a mod-only fix or a documentation-only change a patch release; that every release PR updates every file in its table and the CHANGELOG; and that a fresh Haiku subagent checks the release before the PR is opened and reports a verdict without editing anything. Before 1.0.0 the old rules held. CONTRIBUTING.md points at it. The tag is still proposed after the merge and waits for the maintainer.

  • The people controls share the wall's one control row (#245, #246). With two or more people reporting, Group by and Order by join the status filter, Sort by and the text filter in an outlined People group, so the wall spends one row on controls, or two when the window is narrow, never three. Group by's None is now All, and a new Me appears while someone is signed in as a person: a flat wall of just their orchestrators. Who "me" is now comes from the browser's sign-in (the one Respond uses), so You are, Only me, the wall's "Showing X / Show everyone" line and the podium.me setting are gone; the left list's person headers and All orchestrators still narrow and widen the wall. Where the caption "N shown · M cleared today" does not fit, it is an info icon with the same words as its tooltip. Upgrade note: signing in needs the Admin's respond and activity-feed flags to be on; with them off nobody is signed in, so there is no Me and no "you" marking. See SPEC (People view) and mockup/podium-wall-controls.html.

v0.14.3

Choose a tag to compare

@eengelking eengelking released this 16 Oct 18:38
db14c29

Added

  • The server refuses a request whose Host is not one it expects (#256). A page on another site that points its own name at 127.0.0.1 (DNS rebinding) could read the console's open routes, including every agent's prompt and answer. Now a Host that is not localhost, 127.0.0.1, [::1], the --bind address or a name in the new PODIUM_ALLOWED_HOSTS setting (podiumctl config set PODIUM_ALLOWED_HOSTS=box.lan,...) gets 403, and a server bound to 0.0.0.0 also takes any IP address. The refusal is logged as http.host_refused with the host only. Upgrade note: a reporter on another machine whose serverUrl uses a name (http://box.lan:4317) is refused until that name is in PODIUM_ALLOWED_HOSTS and the server is restarted; a serverUrl that uses an address keeps working on a 0.0.0.0 bind. See DECISIONS 33.
  • Wrong admin-secret guesses sent as X-Podium-Token are throttled (#255). Five different wrong secrets from one address on POST /report, POST /hook, DELETE /sessions/:id or the mod's respond routes now get 429 with Retry-After for 15 minutes, counted apart from the Admin and sign-in lockout. Person tokens are never throttled, and one reporter repeating a stale credential throttles nobody. While an address is throttled even the right admin secret is refused from it. The refusal is logged as auth.failed and shown on the Reporters page. The server warns at start when PODIUM_TOKEN is under 24 characters. Server only.
  • Security sweep (#249). Every response now carries a content security policy, X-Frame-Options: DENY, X-Content-Type-Options: nosniff and Referrer-Policy: no-referrer; the container runs with all capabilities dropped and no way to gain privileges; and express, qs, body-parser and proxy-addr moved to patched versions, so npm audit --omit=dev is clean. The findings, and the risks accepted, are in docs/DECISIONS.md (31).

Changed

  • Podium and podium-reporter share one version. The app's package.json files were still at 0.1.0 while the reporter had reached 0.14.3; all of them are now 0.14.3, and every release sets them together (scripts/versions.test.ts checks the mod, the three manifests and the lockfile; CLAUDE.md lists the files). Releases 0.2.0 to 0.14.3 were tagged afterwards at the commit where the reporter's version changed.
  • Spend by person or group moved from the fleet page's right panel to its own page, Admin > Spend (#239). The page is in the Activity group, above the Admin log. It has Group by (People, Groups) and Show as (Chart, Table), the total spent and the live session count at the top right, and Admin's pager (25, 50 or 100 rows per page, largest first); the total and the table's Total row count everyone, whatever page is showing. The right panel on the fleet page is now the activity list alone, with no empty band and no "Each bar is one person..." line, and the wall's people bands still show each person's share of today's spend. Only an Administrator can open Admin, so other people no longer see the per-person list. The page remembers its own choices in localStorage under podium.adminSpend; the old chart or table choice in podium.people is ignored. The mockup is mockup/podium-admin-spend.html.
  • Admin access is one model (#215). The Admin button in the top bar shows only to someone signed in to the console as an Administrator, or as Admin with the secret. That person opens Admin with no second sign-in, including after the idle lock. Anyone else goes to #/admin, whose sign-in now takes a name and a token (an Administrator's own, or Admin with the admin secret) instead of a token alone. POST /api/admin/unlock takes { name, token } and answers a wrong pair with 401 wrong name or token; POST /api/admin/unlock/from-respond is new; the podium_respond cookie is scoped to /api. Anything that called unlock with { secret } must send name too.
  • Admin > People names a person's token the way Reporters does, as pdm_ and its twelve-character key id, such as pdm_35fa525ce1b4, instead of pdm_… and the last four characters of the secret (#218). The key id only finds a person and is not a secret; no part of the secret is shown or sent to the page any more. The token itself, and what is stored, are unchanged.
  • bin/podiumctl.mjs is now bin/podiumctl, so it runs as bin/podiumctl status with no node in front (#227). package.json's bin entry and npm run podiumctl follow. A boot unit made by podiumctl enable before this change still points at the old file and stops working: podiumctl status now says on boot: enabled, but stale for it, and running podiumctl enable again rewrites it.
  • docs/GOTCHAS.md has a new "Reporters and machines" section (#237): why the Reporters page can show several rows for one machine (two machine ids, one row per account, a reporter with no name, rows left by container restarts), and why a running Claude Code session cannot be handed to another Podium user and shows "refused: session belongs to someone else". The README and the User Guide's Reporters entry link to it.
  • The orchestrator page's plan shows 10 steps at a time. A plan of 11 steps or more has the Files tab's pager under the list ("Showing 1–10 of 23", Previous, page numbers, Next); a plan of 10 or fewer has none. The count and progress bar above the list still cover the whole plan. The panel opens on the page holding the current step, stays on the page you chose when the plan changes, and falls back only when that page no longer exists. The pager is now one shared component, PageNav, used by Plan and Files. UI only. (#214)
  • THIRD_PARTY_NOTICES.md moved into docs/ with the other standing docs, and every reference follows it, including the Theme credits link in the left rail footer (#212).
  • The docs now say that a machine holds one saved Podium login per Claude config, why (Claude Code gives a reporter one shared set of settings and no per-session login, and tells every running session when they change), and that PODIUM_USER and PODIUM_TOKEN are the way to hold two logins at once. A later Claude Code release may remove the limit. Documentation only; the reporter is unchanged. (#230)
  • The top bar's pill names the page it is on: "Orchestrator" on an orchestrator's page (it said "Fleet" there), "Fleet" on the wall, "History" and "Admin" as before. The text now comes from the page, so a new page cannot inherit the wrong name. UI only. (#225)

Fixed

  • npm run podium:smoke passes on a clean checkout again. Vite listened only on ::1 here while the script curled 127.0.0.1, so the proxy check found nothing and the 15-second wait for it pushed the simulator past the direct stream's 34-second --max-time, which then missed the removed events. The script now starts Vite with --host 127.0.0.1 and lets the stream run to 90 seconds. The server was sending removed correctly. Script only. (#259)
  • A reporter's detail puts Ignore… beside Remove from list (#247). Both buttons now sit together at the right of the header, with the name and state on the left, and they stay together when the header wraps or Remove turns into its confirmation. Ignore… keeps its ellipsis, because it opens a choice before anything happens, and now has a tooltip that says so. UI only.

Documentation

  • The README's screenshot (docs/images/podium-chaos.png) is refreshed: nine simulated sessions with running, stale and failing orchestrators, context blowouts and the activity feed.
  • The podium-reporter option descriptions now match the code: Status line shows the real text (podium v<version> ● N live, with the offline, connecting and token-refused states and the update hints), and Podium token and Your name in Podium say that the PODIUM_TOKEN and PODIUM_USER environment variables override them at a session's first start. No behavior change. Reporter 0.14.3. (#252)
  • docs/fixtures/report.sample.json is now a current-format report: reporter 0.14.2 with every optional field filled in (activityFeed, activity, background, respond, asks, clearedFrom, userName, machine), so checkReporter no longer calls it outdated. SPEC 6.2 says so. Tests that want a plain session read it through fixtureReport(), which leaves out the fields that start behavior of their own. No change to the report protocol. (#253)
  • A documentation sweep (#248): corrected stale non-goals, the respond cookie path, the container bind note, the status-line examples, SPEC cross-references and sign-in wording, and the User Guide's tile names, refresh times and log events. Added the model prices, wall notices, compatibility, stream and Admin page parts to ARCHITECTURE, marked entry 7 of DECISIONS as superseded in part, removed three duplicate bullets from this file and merged its repeated headings. In-app and podiumctl copy now matches the code: the Admin button tooltip, the hooks-only tooltip, the Forget old reporters note, token --rotate and the sign-in help. (#248)

v0.14.2

Choose a tag to compare

@eengelking eengelking released this 16 Oct 18:38
308d44b

Changed

  • A context bar turns red at 80% and above (#213, mockup/podium-context-bar.html). From 75% to 79% it stays amber; the red is the theme's own red, the one the level 2 blowout badge uses, in every theme and in light and dark. The red bar has an outline and a solid cap at the end of its fill so it still differs from amber in grayscale, and its accessible name now ends "nearly full". It applies everywhere the bar shows: the card, the orchestrator page, the agent tree and rows, and both detail panels.

Fixed

  • Two Podium identities can report from one machine at once, and signing in as another person no longer turns a running session into a refused one (#226). The reporter's name and token are one setting per Claude config, and Claude Code reloads every running session's reporter when it changes, so a session that had been reporting as Admin began sending the other person's token and was refused. A session now takes its name and token once, at its start, and keeps them; a reload with new settings does not change them. A terminal can also name itself with the PODIUM_USER and PODIUM_TOKEN environment variables, which are used ahead of the settings. To change who a running session reports as, start a new session. Reporter 0.14.2.
  • Admin > Reporters now lists every account that reports from one machine. Two accounts on a machine send the same machine id, so they shared one row and each report overwrote the other's person and token. The first account keeps the machine id as its key and each other account is keyed <machine id>#<person>, so rows and ignore rules made before this still match. An ignore rule on a machine id still covers every account on it. (#221)