(MOT-4354) feat(console): functions and triggers pages with live activity, plus workers drill-down - #737
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe PR adds embedded functions and triggers catalogue pages, live engine data handling, invocation and trigger controls, shared catalogue styling, and expandable worker details in the web console. ChangesCatalogue UI integration
Worker surfaces
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
skill-check — worker0 verified, 57 skipped (no docs/).
Four for four. Nicely done. |
The last two views that existed only in the old iii console now ship from the console worker's own injectable UI: functions at #/ext/functions and triggers at #/ext/triggers. Injected rather than first-party SPA pages, so the console stays a thin host and both pages rebuild, hot-reload, and toggle with the rest of the injectable UI. Functions lists engine::functions::list grouped by namespace, with search, an internal-functions toggle (off by default, or the console's own per-tab handlers bury everything else), and a detail pane that fetches engine::functions::info per selection for the request and response schemas, the triggers bound to that function, and an invoke panel whose body opens on a template generated from the request schema. Triggers keeps two lists distinct that the old view conflated: trigger TYPES from engine::triggers::list (what can fire) and live BINDINGS from engine::registered-triggers::list (what will fire, into which function). Types with no bindings still list but collapse by default; a binding whose type is missing from the catalogue gets its own heading rather than vanishing. Selecting a binding offers a call panel templated from the type's payload schema, labelled for what it actually does: it calls the bound function directly, so the trigger's own config filters do not apply. Neither page polls. Both read on mount, refresh on worker lifecycle events, and expose an explicit refresh control, since the engine publishes no function-registration event to subscribe to.
… real fire path The first cut listed bindings generically: function id as the title, raw config as the subtitle, one call-the-function panel for everything. That loses what the old console got right, which was that a trigger is only legible in the words of its own family. Bindings are now named by what they listen to. An http binding reads `POST /memory/list`, a cron binding reads its schedule in words, a queue subscriber reads its topic, a state binding reads scope/key. The target function moves to the subtitle with its description, so a row says both what fires and what runs. Filter chips across the top count the families actually present (http, cron, queue, state, stream, hook, console asset, event) and narrow the list to one of them, and the group header carries the family tag in that family's tone. The detail pane then offers that family's REAL fire path rather than a uniform function call: - http sends an actual request to the endpoint, with the method picker, path parameter inputs parsed from the `:param` template, add/remove query rows, a JSON body for the methods that take one, and the status and duration of the response. The base URL is read from the http worker's own configuration entry, never guessed. - a queue subscriber publishes to its topic through `iii::durable::publish`, behind a confirm step, so retry and dead-letter behavior applies exactly as it would in production. - cron shows the schedule in words with the raw expression, a next-run estimate for the shapes where that follows from the fields alone, and a run-now that calls the target with a cron-shaped payload, saying so. - everything else keeps the direct call, still labelled as a direct call. Schedule reading lives in cron.ts, family reading in trigger-kinds.ts, so the list and the detail pane cannot disagree about what a binding is.
…ctivity, schema tables The pages now read the engine the way the rest of the console does: over its own signals, never a timer. `engine::functions-available` and `engine::workers-available` are internal engine trigger types that fire when functions register or unregister and when a worker connects or drops. Every catalogue page subscribes to them, so a worker starting fills its rows in within a beat and rows that arrived on the last tick flash once. A `live` marker in each header says so, and the refresh control stays for the impatient. New page, fleet (#/ext/fleet): the connected workers with runtime, version, uptime, function count and what is running right now, and a detail pane that answers in one place what the console used to split across three — this worker's functions with their descriptions, the trigger types it publishes, the live bindings pointing into it, and its reported heap, rss, cpu and event-loop lag. It registers as `fleet` rather than `workers` so it does not collide with the SPA's native tab while both exist. Functions gains two panes: - activity, a live feed of that function's calls read from the trace stream (`trace` trigger tick, then `engine::traces::list` filtered to the span name). Each row carries time, worker, and duration on an adaptive scale, because bus calls are routinely tens of microseconds and a fixed ms scale prints a wall of zeroes. Expanding a row shows the recorded input and output; replaying one drops that input into the invoke editor, minus the engine-injected caller id, which is not something a caller would send. - request and response schemas as field tables — name, type, required, default, description, nested fields indented — instead of raw draft-07. The raw schema still renders for the shapes a table cannot express. The invoke panel now feeds the schema's field names to Monaco as completions, checks the schema's own required list before spending a call, keeps this session's attempts for one-click reuse, and copies the call as an `iii trigger` command line.
…triggers Drops the separate fleet page in favour of the Workers table the console already has: clicking a connected worker expands the row into the functions it registered, the trigger types it publishes, and the live bindings pointing into it, from one `engine::workers::info` call made only when a row is opened. That is where the question gets asked, so that is where it should be answered. A second worker page competing with this one would have split the answer across two places, which is what this whole effort is trying to undo. The deeper reads stay on the dedicated pages, which remain first-class: a function's schemas, invoke panel and call history on #/ext/functions, a binding's fire path on #/ext/triggers. Stopped supervisor entries are not expandable — `engine::workers::info` answers for the live bus, not for a worker that is not on it.
The workers table wrapper is `whitespace-nowrap` so the columns never wrap mid-row, and the expanded cell inherited it: every function description ran straight over the next column. The surface is prose, so it opts back out. Bindings also stack the trigger type above the function id now. Sharing one line squeezed two long strings into a column narrow enough to break them mid-word (`harness::on-s / ession-delete / d`), and `break-words` replaces `break-all` so ids only break when they genuinely have to. Columns go two-up on medium and three-up on extra-large rather than jumping straight to three, which is what made them narrow enough to notice.
…rst triggers view
The pages were live in the narrow sense — one selected function's feed —
while the page itself sat still during a harness turn. Now both pages ride
the `iii:devtools:all-spans` stream (the traces masthead's own feed, one
subscription per page):
- a NOW strip under the head shows the last few executions as they happen,
newest sliding in ease-out, `running` until the completed span replaces
the in-flight one by identity. Clicking an entry selects that function.
- the exact row whose function ran pulses, and carries a quiet right-aligned
`8.0ms · 11s ago` that updates with traffic.
- trigger groups order by last-fired, then registration count, then name —
during a turn the page reads as what the agent is doing, not as an
alphabetical index that opens on `configuration`.
The triggers view also stops burying the operator. Per-tab delivery
handlers (`iii::` prefixed), injected-UI assets, `::ui-content` functions,
and configuration hot-reload hooks are real registrations but never what
the page is opened FOR — they fold into one collapsed `plumbing` section at
the bottom (16 of the old top-of-page rows were exactly these). Session
deliveries summarize as `session …<id>` instead of raw config JSON, and a
detail title can no longer be a `{…}` one-liner.
In-flight spans stream with a null end; Number(null) is 0, which read as a
negative duration. Only an end after the start counts, and until then the
entry says `running`.
… worker config fallback Searching config surfaced harness::triggers::list because its DESCRIPTION mentions config; search now matches function/type ids and worker names only. The invoke panel's copy button says copy iii command and explains itself. The http tester resolves its base URL from the http worker's configuration entry first, falling back to the deprecated iii-http name.
…ue pages The redesign changed two contracts under these pages: pages render inside closable workspace panes with the shared PageShell/PageHeader/PageBody chrome, and structure comes from surface steps, not drawn lines — the rule tokens are literally transparent now, so every border the pages relied on had silently vanished. Both pages now compose from the shared Page components (identity, count as the description, actions, the standard pane close), and the stylesheet speaks the surface language end to end: rows hover and select by fill, chips, tags, tiles and filter pills are alpha fills, error notes sit on the alert-muted step, the cron schedule card on warn-muted, the http endpoint bar on accent-muted, the schema table zebra-fills, and the plumbing section separates by spacing and opacity. Focus keeps its sanctioned ring. The injectable-UI toggle board gets the same conversion. A count badge inside a tab label is a separate word, not a suffix — `triggers 1`, never `triggers1`.
Ten findings from review, all verified against the code first. The two that mattered: listCalls still divided a null end_time into a negative duration for in-flight spans — the same guard spansFromFrame already has (`end > start`, else 0) now applies there too. And two form components reset themselves on OBJECT IDENTITY (HttpTester on the binding, InvokePanel on the request schema); live catalog refreshes rebuild those objects every tick, so an open form wiped itself mid-typing. Both now key their reset effects on the values that name the selection (method/path/params, function id + schema content). The rest: QueuePublish discards an in-flight publish result if the topic changed under it (and clears `sending` on switch); the CLI copy string shell-escapes embedded single quotes; CopyButton only reports "copied" after the clipboard write resolves; the registered-trigger config tab parses `config_summary` (a JSON string on the wire) so the fallback renders structured, not one quoted line; activity rows key and open on a collision-proof row id instead of a possibly-empty span id; the workers-table name cell is a span (a div is invalid inside the expand button) and the toggle points at its detail row via aria-controls; the functions empty-state no longer claims descriptions are searched — that search was removed.
8bfe054 to
84593fd
Compare
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (5)
console/ui/styles.css (1)
464-472: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd the standard
line-clampnext to the prefixed property.The rule uses only
-webkit-line-clamp. Add the standardline-clampdeclaration so the clamp survives browsers that drop the prefixed alias.🎨 Proposed refactor
[data-iii-ui="console"] .console-catalog-row .secondary { font-size: 11px; line-height: 1.45; color: var(--color-ink-faint); overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; + line-clamp: 2; -webkit-box-orient: vertical; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@console/ui/styles.css` around lines 464 - 472, Add the unprefixed line-clamp declaration alongside -webkit-line-clamp in the .console-catalog-row .secondary rule, preserving the existing two-line clamp behavior and prefixed fallback.console/ui/src/catalog/widgets.tsx (2)
184-186: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMake the chip sort explicit.
Array.prototype.sortwith no comparator converts each[key, count]entry to the string"key,count". The result happens to be key order, but the intent is hidden. Sort on the key.♻️ Proposed change
- const entries = [...counts.entries()].filter(([, n]) => n > 0).sort() + const entries = [...counts.entries()] + .filter(([, n]) => n > 0) + .sort(([a], [b]) => a.localeCompare(b))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@console/ui/src/catalog/widgets.tsx` around lines 184 - 186, Update the entries sorting in the counts-rendering logic to pass an explicit comparator that compares each entry’s key, rather than relying on the default Array.prototype.sort string conversion; preserve the existing positive-count filtering and subsequent total calculation.
266-285: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueClear the confirmation timer on unmount.
The 2-second timer runs after the component unmounts. It then calls
setCopiedon an unmounted component. Keep the timer id in a ref and clear it in an effect cleanup.♻️ Proposed change
const [copied, setCopied] = useState(false) + const timerRef = useRef<number | null>(null) + useEffect( + () => () => { + if (timerRef.current !== null) window.clearTimeout(timerRef.current) + }, + [], + ) return ( @@ setCopied(true) - window.setTimeout(() => setCopied(false), 2000) + if (timerRef.current !== null) window.clearTimeout(timerRef.current) + timerRef.current = window.setTimeout(() => setCopied(false), 2000)Add
useEffectanduseRefto the existingreactimport.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@console/ui/src/catalog/widgets.tsx` around lines 266 - 285, Update the copy button component around the copied state to store the confirmation timeout ID in a ref, and add an effect cleanup that clears the timer on unmount. Use the existing React import with useEffect and useRef, and ensure the click handler replaces or records the timeout through the ref while preserving the current copied-label behavior.console/ui/src/catalog/HttpTester.tsx (1)
160-181: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winAdd a deadline to the HTTP test request.
fetch(url, init)has no request timeout, so a server that accepts the connection can keepsendingtrue indefinitely. Pass an abort signal with a timeout to the request init.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@console/ui/src/catalog/HttpTester.tsx` around lines 160 - 181, Update the HTTP request flow around fetch(url, init) to provide an abort signal with a finite timeout, using the request init passed to fetch and preserving the existing success, error, and cleanup handling. Ensure stalled requests abort so sending does not remain true indefinitely.console/ui/src/catalog/engine.ts (1)
326-329: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueUse a ref or
useIdfor the per-mount handler id.
useMemois a performance hint with unchanged dependencies and does not guarantee identity across future React optimization passses. If it recompiles here,handlerIdchanges and the effect re-runs, sooffHandlercan unregister the mounted handler and a new handler can register a new copy of the component. A lazily initializeduseRefpreserves identity for the mount.♻️ Proposed change
- const handlerId = useMemo(() => { - hubSeq += 1 - return `iii::console-catalog::live-${hubSeq}` - }, []) + const idRef = useRef<string>('') + if (!idRef.current) { + hubSeq += 1 + idRef.current = `iii::console-catalog::live-${hubSeq}` + } + const handlerId = idRef.currentAlso applies to
useSpanFeed.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@console/ui/src/catalog/engine.ts` around lines 326 - 329, Replace the useMemo-based handler ID initialization in the catalog component and useSpanFeed with a lazily initialized useRef or useId so each mount retains a stable handlerId across renders; preserve the existing unique ID format and effect registration behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@console/ui/src/catalog/HttpTester.tsx`:
- Around line 122-126: Update the path replacement inside the binding.params
reduce in HttpTester so each parameter name matches the complete path token
rather than a prefix of a longer token; preserve the existing encoded-value and
fallback behavior while ensuring cases like :id do not replace the :id portion
of :idx.
In `@console/ui/src/catalog/InvokePanel.tsx`:
- Around line 63-70: Update the fast path in the payload mapping around
InvokePanel’s args construction to leave values unquoted only when they contain
a strictly shell-safe character set; quote values containing shell
metacharacters such as $, ;, |, &, parentheses, redirection, glob, or backslash
characters. Preserve the existing single-quote escaping behavior for all values
that do not qualify as safe.
In `@console/ui/src/catalog/TriggersPage.tsx`:
- Around line 843-848: Update the fallback hint in the trigger timing display
near untilLabel and nextCronRun so it does not claim null results are caused
only by date or weekday restrictions. Replace it with neutral wording that
accurately covers all unsupported expression shapes, including nonzero seconds
and stepped hours.
- Around line 734-750: Update the binding-function render path around
InvokePanel so it is deferred until the trigger resource has settled and
type.data.request_schema is available. Keep the existing InvokePanel props and
behavior unchanged once the schema is loaded, preventing its schema-reset effect
from clearing user-entered content during late schema arrival.
In `@console/ui/styles.css`:
- Around line 1075-1080: Update the background value in the
.console-catalog-result-head .dot rule to use the lowercase CSS-wide keyword
currentcolor instead of currentColor, resolving the Stylelint value-keyword-case
error.
In `@console/web/src/pages/Workers/components/WorkersTable.stories.tsx`:
- Around line 142-145: Update the expanded-story documentation comment for
WorkerSurface to describe only the registered functions and trigger types it
renders; remove the claim about bindings pointing into the row.
---
Nitpick comments:
In `@console/ui/src/catalog/engine.ts`:
- Around line 326-329: Replace the useMemo-based handler ID initialization in
the catalog component and useSpanFeed with a lazily initialized useRef or useId
so each mount retains a stable handlerId across renders; preserve the existing
unique ID format and effect registration behavior.
In `@console/ui/src/catalog/HttpTester.tsx`:
- Around line 160-181: Update the HTTP request flow around fetch(url, init) to
provide an abort signal with a finite timeout, using the request init passed to
fetch and preserving the existing success, error, and cleanup handling. Ensure
stalled requests abort so sending does not remain true indefinitely.
In `@console/ui/src/catalog/widgets.tsx`:
- Around line 184-186: Update the entries sorting in the counts-rendering logic
to pass an explicit comparator that compares each entry’s key, rather than
relying on the default Array.prototype.sort string conversion; preserve the
existing positive-count filtering and subsequent total calculation.
- Around line 266-285: Update the copy button component around the copied state
to store the confirmation timeout ID in a ref, and add an effect cleanup that
clears the timer on unmount. Use the existing React import with useEffect and
useRef, and ensure the click handler replaces or records the timeout through the
ref while preserving the current copied-label behavior.
In `@console/ui/styles.css`:
- Around line 464-472: Add the unprefixed line-clamp declaration alongside
-webkit-line-clamp in the .console-catalog-row .secondary rule, preserving the
existing two-line clamp behavior and prefixed fallback.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 1e169c52-5731-4383-ae40-94ad3779da66
📒 Files selected for processing (22)
console/build.rsconsole/src/ui.rsconsole/ui/build.mjsconsole/ui/catalog-page.tsxconsole/ui/src/catalog/ActivityFeed.tsxconsole/ui/src/catalog/FunctionsPage.tsxconsole/ui/src/catalog/HttpTester.tsxconsole/ui/src/catalog/InvokePanel.tsxconsole/ui/src/catalog/QueuePublish.tsxconsole/ui/src/catalog/SchemaTable.tsxconsole/ui/src/catalog/TriggersPage.tsxconsole/ui/src/catalog/cron.tsconsole/ui/src/catalog/engine.tsconsole/ui/src/catalog/live.tsxconsole/ui/src/catalog/schema.tsconsole/ui/src/catalog/trigger-kinds.tsconsole/ui/src/catalog/widgets.tsxconsole/ui/styles.cssconsole/web/src/pages/Workers/components/WorkerSurface.tsxconsole/web/src/pages/Workers/components/WorkersTable.stories.tsxconsole/web/src/pages/Workers/components/WorkersTable.tsxconsole/web/src/pages/Workers/fixtures/workers-fixtures.ts
| const args = Object.entries(payload).map(([key, value]) => { | ||
| const literal = | ||
| typeof value === 'string' ? value : (JSON.stringify(value) ?? '') | ||
| if (!/[\s"']/.test(literal)) return `${key}=${literal}` | ||
| // A single quote cannot appear inside single quotes: close the segment, | ||
| // emit an escaped quote, reopen. | ||
| return `${key}='${literal.replaceAll("'", `'\\''`)}'` | ||
| }) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Quote every value that a shell can interpret.
The fast path emits key=value unquoted when the value has no whitespace and no quote characters. Other shell metacharacters pass through unquoted. A value such as $(id), a;b, a|b, or a&b produces a copied command that the shell rewrites or splits. The pasted call then sends a different payload than the panel sent.
Restrict the unquoted path to a safe character set.
🐛 Proposed fix
const args = Object.entries(payload).map(([key, value]) => {
const literal =
typeof value === 'string' ? value : (JSON.stringify(value) ?? '')
- if (!/[\s"']/.test(literal)) return `${key}=${literal}`
+ // Only characters no POSIX shell reinterprets may go unquoted.
+ if (/^[A-Za-z0-9_@%+=:,./-]*$/.test(literal)) return `${key}=${literal}`
// A single quote cannot appear inside single quotes: close the segment,
// emit an escaped quote, reopen.
return `${key}='${literal.replaceAll("'", `'\\''`)}'`
})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const args = Object.entries(payload).map(([key, value]) => { | |
| const literal = | |
| typeof value === 'string' ? value : (JSON.stringify(value) ?? '') | |
| if (!/[\s"']/.test(literal)) return `${key}=${literal}` | |
| // A single quote cannot appear inside single quotes: close the segment, | |
| // emit an escaped quote, reopen. | |
| return `${key}='${literal.replaceAll("'", `'\\''`)}'` | |
| }) | |
| const args = Object.entries(payload).map(([key, value]) => { | |
| const literal = | |
| typeof value === 'string' ? value : (JSON.stringify(value) ?? '') | |
| // Only characters no POSIX shell reinterprets may go unquoted. | |
| if (/^[A-Za-z0-9_@%+=:,./-]*$/.test(literal)) return `${key}=${literal}` | |
| // A single quote cannot appear inside single quotes: close the segment, | |
| // emit an escaped quote, reopen. | |
| return `${key}='${literal.replaceAll("'", `'\\''`)}'` | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@console/ui/src/catalog/InvokePanel.tsx` around lines 63 - 70, Update the fast
path in the payload mapping around InvokePanel’s args construction to leave
values unquoted only when they contain a strictly shell-safe character set;
quote values containing shell metacharacters such as $, ;, |, &, parentheses,
redirection, glob, or backslash characters. Preserve the existing single-quote
escaping behavior for all values that do not qualify as safe.
| ) : binding.function_id ? ( | ||
| <InvokePanel | ||
| host={host} | ||
| functionId={binding.function_id} | ||
| requestSchema={type.data?.request_schema} | ||
| label={ | ||
| spec.family === 'cron' ? 'run now' : 'trigger function' | ||
| } | ||
| runningLabel={ | ||
| spec.family === 'cron' ? 'running…' : 'triggering…' | ||
| } | ||
| hint={ | ||
| spec.family === 'cron' | ||
| ? `Triggers ${binding.function_id} with a cron-shaped payload. The schedule is untouched, and the next scheduled firing still happens.` | ||
| : `Triggers ${binding.function_id} directly with this payload. The binding itself is bypassed, so its config filters do not apply.` | ||
| } | ||
| /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
The late schema arrival clears a typed request body.
type.data is undefined on the first render, so InvokePanel receives requestSchema={undefined}. InvokePanel keys its reset effect on JSON.stringify(requestSchema). When engine::triggers::info resolves, that key changes and the effect replaces the editor content with the new template. Text typed during the load is lost.
Render the panel after the resource settles.
🐛 Proposed fix
- ) : binding.function_id ? (
+ ) : binding.function_id ? (
+ type.data === null && !type.error ? (
+ <Note>loading the payload schema…</Note>
+ ) : (
<InvokePanel
host={host}
functionId={binding.function_id}
requestSchema={type.data?.request_schema}
@@
/>
+ )
) : (🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@console/ui/src/catalog/TriggersPage.tsx` around lines 734 - 750, Update the
binding-function render path around InvokePanel so it is deferred until the
trigger resource has settled and type.data.request_schema is available. Keep the
existing InvokePanel props and behavior unchanged once the schema is loaded,
preventing its schema-reset effect from clearing user-entered content during
late schema arrival.
| value={next ? untilLabel(next, now) : 'not derivable'} | ||
| hint={ | ||
| next | ||
| ? next.toLocaleTimeString() | ||
| : 'the expression restricts dates or weekdays' | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Correct the "not derivable" hint.
nextCronRun returns null for more shapes than date and weekday restrictions. It also returns null when the seconds field is not 0 and when the hour field is a step, for example 0 0 */2 * * *. For those expressions the hint states a wrong reason. Use a neutral wording, or derive the reason from the expression.
🐛 Proposed fix
hint={
next
? next.toLocaleTimeString()
- : 'the expression restricts dates or weekdays'
+ : 'this schedule shape is not derivable here — read the expression'
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| value={next ? untilLabel(next, now) : 'not derivable'} | |
| hint={ | |
| next | |
| ? next.toLocaleTimeString() | |
| : 'the expression restricts dates or weekdays' | |
| } | |
| value={next ? untilLabel(next, now) : 'not derivable'} | |
| hint={ | |
| next | |
| ? next.toLocaleTimeString() | |
| : 'this schedule shape is not derivable here — read the expression' | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@console/ui/src/catalog/TriggersPage.tsx` around lines 843 - 848, Update the
fallback hint in the trigger timing display near untilLabel and nextCronRun so
it does not claim null results are caused only by date or weekday restrictions.
Replace it with neutral wording that accurately covers all unsupported
expression shapes, including nonzero seconds and stepped hours.
| [data-iii-ui="console"] .console-catalog-result-head .dot { | ||
| width: 6px; | ||
| height: 6px; | ||
| border-radius: 50%; | ||
| background: currentColor; | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the Stylelint value-keyword-case error.
Stylelint reports an error on Line 1079. Use the lowercase CSS-wide keyword currentcolor.
🎨 Proposed fix
[data-iii-ui="console"] .console-catalog-result-head .dot {
width: 6px;
height: 6px;
border-radius: 50%;
- background: currentColor;
+ background: currentcolor;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| [data-iii-ui="console"] .console-catalog-result-head .dot { | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| background: currentColor; | |
| } | |
| [data-iii-ui="console"] .console-catalog-result-head .dot { | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| background: currentcolor; | |
| } |
🧰 Tools
🪛 Stylelint (17.14.0)
[error] 1079-1079: Expected "currentColor" to be "currentcolor" (value-keyword-case)
(value-keyword-case)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@console/ui/styles.css` around lines 1075 - 1080, Update the background value
in the .console-catalog-result-head .dot rule to use the lowercase CSS-wide
keyword currentcolor instead of currentColor, resolving the Stylelint
value-keyword-case error.
Source: Linters/SAST tools
| /** | ||
| * A connected row expanded into its surface: the functions it registered, | ||
| * the trigger types it publishes, and the bindings pointing into it. Click | ||
| * the `harness` row in the canvas to open it. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Correct the expanded-story description.
WorkerSurface renders registered functions and trigger types. It does not render registered trigger bindings. Remove “and the bindings pointing into it” from this description.
Proposed fix
- * the trigger types it publishes, and the bindings pointing into it. Click
+ * the trigger types it publishes. Click📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** | |
| * A connected row expanded into its surface: the functions it registered, | |
| * the trigger types it publishes, and the bindings pointing into it. Click | |
| * the `harness` row in the canvas to open it. | |
| /** | |
| * A connected row expanded into its surface: the functions it registered, | |
| * the trigger types it publishes. Click | |
| * the `harness` row in the canvas to open it. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@console/web/src/pages/Workers/components/WorkersTable.stories.tsx` around
lines 142 - 145, Update the expanded-story documentation comment for
WorkerSurface to describe only the registered functions and trigger types it
renders; remove the claim about bindings pointing into the row.
What
Functions and Triggers as first-class console pages, shipped as the console worker's own injectable UI (
console/ui, route#/ext/functionsand#/ext/triggers), plus per-worker drill-down on the Workers table. Closes the last user-facing gap with the oldiii-hq/iii/consolecatalogue views ahead of its deprecation.Functions (
#/ext/functions)engine::functions::list, grouped by namespace, searched by id/workerengine::functions::info: request/response schemas rendered as field tables (name, type, required, default, description; raw JSON fallback for shapes a table cannot express), registered triggers on the functioniii triggercommand line_caller_worker_idstripped)Triggers (
#/ext/triggers)engine::triggers::list) with their live REGISTERED TRIGGERS (engine::registered-triggers::list) grouped underneath — the two lists the old view conflatedPOST /users/:id, the schedule in words, the queue topic,scope/key— never raw config JSON as a title:paramtemplate, query rows, JSON body); queue subscribers publish throughiii::durable::publishbehind a confirm; cron gets schedule + next-run and a clearly-labelled direct call; everything else keeps the direct call, labelled as suchiii::handlers, injected-UI assets, on-config-change hooks) folds into one collapsed section instead of burying the operator; groups order by last-fired, then registration countLive, no polling
Both pages subscribe to the engine's own signals —
engine::functions-available,engine::workers-available, and theiii:devtools:all-spansstream. A NOW strip shows executions as they happen (in-flight spans readrunninguntil their completed span replaces them), the exact row whose function ran pulses, and rows carry a quiet8.0ms · 11s agothat updates with traffic.Workers drill-down
A connected row on the Workers table expands into that worker's functions and trigger types from one
engine::workers::infocall, made only when the row opens. Registered triggers stay on the triggers view by design.Redesign
Composed on the shared
PageShell/PageHeader/PageBodypane chrome, styled to the surfaces-not-borders contract end to end (rows select by fill, status notes on the muted status steps, focus keeps its ring). The injectable-UI toggle board converted to match.Verification
Live against a running engine: real invokes (
state::list_groups,ok · 6ms), a real http request through a registered trigger (200 · 8ms), the live feed picking up externally-fired calls within a beat, both themes, both pane widths.cargo fmt --check,cargo clippy --all-targets -D warnings,cargo test(6 UI asset tests pin page ids and signal subscriptions),tsc, biome, vitest all green.Linear: MOT-4354, MOT-4355, MOT-4356, MOT-3677.
Summary by CodeRabbit