Originally created by: Akarsh-Hegde
Base / scope note: this PR targets
feat/llm-provider-enum, which trailsfeat/llm-provider-connectivity-test(the branch this was cut from) by the whole in-flight LLM-provider + Lab stack. So the raw diff is large (~127 files). The change THIS PR adds is the top two commits (875641ea,8f8a7853) - 11 files underui/components/timeline/llmlab/,src/llm_experiment/,src/pm_worklog/generate.rs, and the trayllm_labcommand. Re-target the base tofeat/llm-provider-connectivity-testfor a clean 2-commit review.
Turns the dev-only LLM Lab from a 1180px modal into its own full-screen surface. A run now shows one variant's timeline at a time with a variant-switcher tab strip, clicking a task card in a fold variant opens a task sidebar beside the timeline, and the sidebar can draft that task's worklog on demand with the shown variant.
LlmLabModal -> LlmLabScreen: full-viewport panel (absolute inset-0, own header, Escape-to-close) replacing the ModalShell wrapper.RunView (new): run header + variant-switcher tab strip + single full-width body. Switching variant clears the task selection - task ids (T1,T2) are per-variant (each model builds its own day), so a stale id must not carry across a switch.VariantBody (extracted from the former ResultsGrid.VariantColumn): one variant, full-width, preserving every ok / failed / rate_limited / pending branch so a non-ok variant shows its status, not a blank pane.LabTaskSidebar (new): Lab-local read-only detail (When + the model's per-task log) from the DayTaskDetail the timeline already emits. Deliberately not DayTaskDetailPanel/useWorklog - those drive production worklog tables for a task id, and a fold's ids are a model's simulated day, not real rows.ResultsGrid.tsx / LlmLabModal.tsx removed; shell import + JSX updated.The sidebar drafts THIS fold task's worklog with whichever variant the timeline is showing - a metered, ephemeral call, gated behind a free/local caution, that writes nothing.
day_tasks row), the draft is built from the task's inline content, never a production lookup:generate.rs: generate_request_from_task builds the worklog-generate request from an inline title/summary/minutes, reusing the real board candidate set + the identical prompt/schema.runner.rs: variant_backend's core is now resolve_backend(provider, model), shared with the draft path (custom:<id> included).cli.rs: meridian llm-experiment draft-task --day --variant --task-json - one completion, prints {"draft": โฆ}, persists nothing.draft_lab_worklog: dev-gated command that shells out to it.LabTaskSidebar: the button + free/local caution + a formatted/raw render of the drafted update (posts-to / summary / sections).A fold DayTask has no drafted-worklog prose - only summary bullets. Real prose comes only from worklog_generate, which reads production day_tasks. So the sidebar shows the summary log for free, and the polished prose is the on-demand, metered draft above - never auto-run.
Unchanged - dev-only (channel === 'dev' in the shell + commands/llm_lab.rs release refusal). This surface does not exist for users.
cargo clippy clean; llm_experiment tests (20) pass.cargo clippy -- -D warnings clean.npm run build (TypeScript pass) + bun test (227 pass).local variant): meridian llm-experiment draft-task --variant local โฆ returns a real drafted worklog in the {update:{summary,sections}} shape the sidebar renders - so generate_request_from_task -> resolve_backend -> complete all run.day_fold run before merge.๐ค Generated with Claude Code
Originally posted by: coderabbitai[bot]
โจ Finishing Touches
๐งช Generate unit tests (beta)
- [ ] Create PR with unit tests - [ ] Commit unit tests in branch `feat/llm-lab-fullscreen`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.
โค๏ธ Share
- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai) - [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai) - [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai) - [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)Comment
@coderabbitai helpto get the list of available commands.Originally posted by: Akarsh-Hegde
Review scope
Per the PR description, I reviewed the actual 2-commit diff (
875641ea,8f8a7853) againstfeat/llm-provider-connectivity-testrather than the raw ~127-file diff against the stalefeat/llm-provider-enumbase โ confirmed this is exactly the 11-file / 563(+)/101(-) change described.Overview
Solid piece of work: turns the LLM Lab modal into a full-screen surface with a variant switcher (
RunView) and a read-only task sidebar (LabTaskSidebar) that can fire an on-demand, ephemeral worklog draft against the currently-shown variant. The Rust side cleanly factorsresolve_backendout ofvariant_backendand adds a parallelgenerate_request_from_tasknext togenerate_requestfor the inline (non-DB) case. I traced the request/response shapes end-to-end (WorkstreamReportfields,DayTaskDetailโdraft_lab_worklogbody โ--task-jsonโInlineTaskโgenerate_request_from_task, and thecustom:<id>/provider:modelvariant-token round-trip betweenRunView.variantToken()andVariant::parse) โ all consistent, no shape mismatches.Findings
1. Module doc drift in
tray/src-tauri/src/commands/llm_lab.rs(minor, but worth a line)The file header still says "Three commands behind the LLM Lab modal" and lists only
run_llm_experiment/get_llm_experiments/get_llm_experimentโdraft_lab_worklogisn't mentioned, and "modal" is now stale terminology (it's a full-screen surface post-LlmLabScreen). Given this repo's convention of keeping module//!docs authoritative (see CLAUDE.md's "Document it (required)" step), this should get a fourth bullet.2. A load-bearing doc claim is now false, in two places
tray/src-tauri/src/commands/llm_lab.rs's header: "Thellm-experimentCLI itself stays ungated for field debugging โ the UI is the gated surface."src/llm_experiment/cli.rs's header doesn't call this out explicitly, but its dev-gating rationale (ungated in release because it "only ever writes the experiment tables") is implicit throughout.draft-taskbreaks that invariant: it's a CLI subcommand that fires a real, metered LLM completion and is not gated bydev_only()โ only the tray'sdraft_lab_worklogwrapper is. So on a shipped release binary,meridian llm-experiment draft-task ...run directly from a terminal will hit a live provider and incur cost, unlike every other subcommand in this file (which only touch local experiment tables). That may well be an acceptable/intentional tradeoff (it's still explicit, deliberate CLI use โ not something a user stumbles into), but the doc comments should say so, since they currently assert the opposite ("only ever writes the experiment tables").3. Sticky variant selection on an in-progress run (design nit, not a bug)
RunView'sfirstOkisuseMemo'd but only consumed viauseState(firstOk), so it's genuinely computed once at mount (matches the comment). If you open a still-running run,selectedIdxstarts at 0 (nookyet) and never auto-advances to the first variant that finishes โ you have to manually click the switcher tab even after your currently-viewed variant fails and a later one succeeds. Probably fine given the explicit "computed once" comment, but worth a decision check: was auto-follow-first-completion considered and deliberately dropped, or just not thought about?4. No automated coverage for the new Rust prompt-assembly / resolution paths
resolve_backend,generate_request_from_task, anddraft_taskhave no unit tests. I manually verifiedgenerate_request_from_task'sWorkstreamReportconstruction is field-for-field identical toload_workstream_report's (so the inline draft path builds the byte-identical prompt shape modulo the DB read), but a unit test assertingbuild_user_promptoutput is equal for an inline task vs. an equivalent DB-loaded one would catch future drift between the two paths cheaply (no network/DB needed for that assertion). The PR description's "Verified" section covers this manually/end-to-end, which is reasonable for a dev-only surface, but the two prompt-builders diverging silently later is exactly the kind of thing a golden-string test would catch.What's solid
DayTaskDetail.{day,title,minutes,summary}โdraft_lab_worklogbody โInlineTaskall line up exactly;DayTaskColumn'sonSelect: (detail: DayTaskDetail | null) => voidsignature matchesVariantBody'sonSelectTaskpassthrough (an improvement over the oldResultsGrid, which downgraded to just an id).variantToken()inRunView.tsxandVariant::parsein the daemon agree oncustom:<id>/provider:model/ bare-providerforms.let-elserefactor inresolve_backend(let Some(provider) = LlmProvider::from_wire(provider) else { ...{provider:?}... }) is correct โ theelsebranch can't see the pattern binding, so the error message correctly prints the original&strargument, not an unbound name. Easy thing to get wrong; it's right here.draft_lab_worklog's JSON-encoded--task-jsonarg goes throughtokio::process::Command::args(...), never a shell, so arbitrary task titles/summaries containing quotes/newlines are safe.draft_lab_worklogcorrectly callsdev_only()first, matches the existing Lab commands' pattern, registered inlib.rs'sinvoke_handler!.ResultsGrid/LlmLabModal/VariantColumn) came back clean โ the rename left no dangling imports.bg-panel,rise,z-40) match existingModalShell/ReportModalconventions exactly.Suggested action
None of the findings are blocking โ [#1] and [#2] are a couple sentences of doc fix-up, [#3] is worth a one-line confirmation from you on intent, [#4] is a nice-to-have. I'd merge after a quick doc pass on [#1]/#2, especially [#2] since it's a security/cost-surface claim that's now inaccurate.
Related
Tickets:
#1Tickets:
#2Tickets:
#3Tickets:
#4Originally posted by: Akarsh-Hegde
Review addressed โ
3f4f65cbThanks for the thorough pass. All four findings resolved:
#2 (metered CLI claim โ the load-bearing one): fixed, and closed the hole rather than just documenting it. Rather than leave
draft-taskungated-with-a-caveat, I gated it: it now refuses in a release build (cfg!(debug_assertions)), matching the tray'sdev_onlygate. This is safe for the dev flow - a debug tray resolvesmeridian_bin()totarget/debug/meridian(debug_assertions on), and in release the tray'sdraft_lab_worklogrefuses before shelling out anyway, so the button never regresses. The cost-surface (a shipped binary runningmeridian llm-experiment draft-taskand hitting a live provider) is gone. Both doc headers (cli.rs, trayllm_lab.rs) now state the invariant accurately: the read/run subcommands stay ungated because they only write local tables;draft-taskis the metered exception and is dev-gated.#1 (module doc drift): fixed. The tray
llm_lab.rsheader now says four commands (addsdraft_lab_worklog), drops the stale "modal" wording for "full-screen surface", and the "# Who calls this" section names the draft invoke.#3 (sticky variant selection): implemented auto-follow. Not intentional before - now
RunViewauto-follows the first variant to finish until the user manually picks a tab, then freezes on that choice. It won't jump back to a lower-index variant that finishes later (guards on the shown variant already beingok). So opening a still-running run lands on whichever model completes first instead of a pending/failed one.#4 (no coverage): added two tests in
generate.rs.build_user_prompt_pins_the_shape_both_draft_paths_share- a golden on the shared assembler bothgenerate_requestandgenerate_request_from_taskfeed.inline_draft_request_matches_the_worklog_generate_contract- assertsgenerate_request_from_taskbuilds the byte-identical system/schema/max_tokens contract as the DB-backed path, and that the inline title/summary/minutes reach the workstream block.Verified: daemon
pm_worklog::generatetests (19) pass; traycargo clippyclean; uinpm run build(TypeScript) +bun test(227) pass; pre-push suite green.Ready to merge from my side. One reminder carried over from the description: the in-app GUI click-through in a live dev tray still hasn't been run (the draft path is verified end-to-end via the CLI, but not the React wiring) - worth a quick smoke test on a real
day_foldrun before merge.Ticket changed by: Akarsh-Hegde