You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As a learner who builds a Pipeline (DAG of Blocks from the Catalog) and runs it as a Job, the current Editor has two places that show execution information: the bottom ConsolePanel (shell) and the right Inspector. The shell makes the page taller than the viewport, so the learner must scroll to see it. The shell and the Inspector duplicate the same information (global logs vs per-Block outputs). The learner cannot easily review past Jobs of the same Pipeline; the shell only shows the current Job.
Solution
Remove the bottom shell completely and centralise execution information in the right sidebar, complemented by transient Toast notifications:
The right sidebar becomes a single seam with three exclusive modes, switched by a ToggleButtonGroup type="single": Inspecteur | Journal | Cours. Cours stays as is. Inspecteur stays per-Block. Journal is new.
Journal is the history reader for the current Pipeline: it lists all Jobs of that Pipeline ordered by createdAt DESC, labelled by time (HH:mm, e.g. 12h04, 12h10). Click on a timestamp selects that Job. The view is a fused chronological timeline that interleaves global consoleLines and per-Blockjob_outputs cards. A slider/toggle Logs | Outputs | Mixte filters the fused view.
The canvas stays on the current Pipeline graph. The Journal adds a Restaurer cette version button that loads the nodes/edges snapshot of the selected past Job into the current Pipeline (backup / time-travel).
On Pipeline run (onRun), the sidebar switches automatically to Inspecteur and selects the last topological Block (the Block with no successors) – the final output of the Pipeline.
Toast (Astryx useToast + ToastViewport) shows only transient status: Job transitions (queued → running → done/error) and per-Block errors. It does not spam per-line logs. Logs stay in the Journal.
User Stories
As a learner, I want the Editor to fit in the viewport without page scroll, so that I see the canvas and the sidebar without sliding down.
As a learner, I want no bottom shell, so that there is one place for execution information, not two.
As a learner, I want a right sidebar with three modes Inspecteur | Journal | Cours, so that I can switch between per-Block details, execution history, and lessons with one control.
As a learner, I want the Journal to list past Jobs of the current Pipeline by time (12h04, 12h10), so that I can find a previous execution.
As a learner, I want to click a timestamp in the Journal to see that Job's execution, so that I can review past outputs.
As a learner, I want the Journal to show a fused timeline that interleaves global logs and per-Block outputs in chronological order, so that I can replay how the Job ran.
As a learner, I want a slider Logs | Outputs | Mixte in the Journal, so that I can filter to only logs, only outputs, or both.
As a learner, I want the canvas to stay on the current Pipeline when I browse the Journal, so that the Pipeline graph does not change unexpectedly.
As a learner, I want a Restaurer cette version button in the Journal, so that I can restore the nodes/edges snapshot of a past Job into the current Pipeline.
As a learner, I want the sidebar to switch automatically to Inspecteur on run and select the last topological Block, so that I immediately see the final output without extra clicks.
As a learner, I want Toast to show Job status changes (queued, running, done, error), so that I know the Job state without looking at the sidebar.
As a learner, I want Toast to show per-Block errors, so that I see failures immediately even if the Journal is not open.
As a learner, I want Toast not to spam per-line logs, so that I am not overwhelmed.
As a learner, I want the Catalog and Pipeline validation to stay unchanged, so that removing the shell does not break Block discovery or Job dispatch.
As a new contributor, I want the ConsolePanel deleted, so that there is one seam for execution history (the Journal) and not two.
As a maintainer, I want the CSS layer order (reset,theme,base,astryx-base,astryx-theme,components,utilities) unchanged, so that the fix does not regress styling.
As a maintainer, I want vite build and vitest to stay green, so that regressions are caught.
Implementation Decisions
Scope is frontend-only.Catalog, Pipeline DAG, and Job execution (local / Vast.ai) stay as per CONTEXT.md. No schema change to jobs / job_outputs; the Journal reuses listPipelineJobs(pipelineId) and getJobOutputs(jobId) already used in EditorPage for the last Job. No ADR yet; this spec will not create one, but a follow-up ADR for "right sidebar as single execution seam" is recommended.
Modules to modify:
Delete frontend/src/components/ui/ConsolePanel.tsx and its usage in frontend/src/pages/EditorPage.tsx; remove consoleLines / jobStatus rendering from the Editor shell. EditorPage layout goes from header + canvas + console to header + canvas (canvas flex:1 min-height:0), outer height:100vh overflow:hidden to avoid page scroll. Remove the local custom frontend/src/components/ui/Toast.tsx (currently a custom div with theme.color.* hardcodes) or rename it — see Toast seam below.
Right sidebar: frontend/src/components/flow/FlowCanvas.tsx currently embeds InspectorPanel + CoursPanel with local rightMode: 'cours'|'inspecteur'. Extract a new RightSidebar seam (either inside FlowCanvas.tsx or new frontend/src/components/flow/RightSidebar.tsx) that owns ToggleButtonGroup with three modes. FlowCanvas keeps collapsible shell logic (rightCollapsed 48/260) but the switch is the single seam.
New frontend/src/components/flow/JournalPanel.tsx: props pipelineId: string | null, selectedJobId: string | null, onSelectJob: (id: string) => void. Internally: useQuery(['jobs', pipelineId], () => listPipelineJobs(pipelineId), {enabled: !!pipelineId}), useQuery(['jobOutputs', selectedJobId], () => getJobOutputs(selectedJobId), {enabled: !!selectedJobId}), and derive consoleLines from selected Job payload if available else empty. Fused timeline: merge consoleLines (global, ordered) with outputs per Block by createdAt (new Date(o.created_at).getTime()); render as VStack gap={2} with Card variant="muted" padding={2} for outputs and Text for logs. Timestamps format HH:mm via toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'}) → 12h04.
Restaurer cette version: in JournalPanel, <Button label="Restaurer cette version" variant="primary" onClick={handleRestore}> calls useAppStore.getState().commitUndoPoint() then loadPipeline(snapshot.nodes, snapshot.edges, snapshot.pipelineId, snapshot.name) where snapshot is the Job's stored nodes/edges if the API returns it, else fallback to getPipeline(selectedJob.pipelineId) fetch. After restore, optional setMode('inspecteur'). Snapshot comes from persisted pipeline graph at job creation time; restoring overwrites current flowNodes/flowEdges (explicit user action, undoable via undo stack).
Auto-switch on run: in frontend/src/hooks/useBlockRunner.tsonRun success (onSuccess of runMutation) or in FlowCanvas/RightSidebaruseEffect watching jobStatus === 'running', call setMode('inspecteur') and setSelectedBlockId(lastTopoNodeId). lastTopoNodeId computed via scan nodes.filter(n => !edges.some(e => e.source === n.id)) and take last (Kahn topo tail). Reuse if already available. Selection is done via useReactFlow().setNodes or store-driven selected flag — keep existing BlockNode selection mechanism (ReactFlow selected).
Toast — Astryx-compliant (CORRECTED):
Current frontend/src/components/ui/Toast.tsx is not Astryx-compliant: it renders a custom div with position:fixed, theme.color.* hardcodes, and a custom useAppStore toast. Delete or replace it.
Correct Astryx API (verified via @astryxdesign/core0.4.6 — npx @astryxdesign/cli docs styling + dist/Toast/types.d.ts): import { useToast, ToastViewport } from '@astryxdesign/core' (re-exported from core/Toast); const showToast = useToast() returns ShowToastFn; signature showToast({ body: ReactNode, type?: 'info' | 'error', isAutoHide?: boolean, autoHideDuration?: number, uniqueID?: string }). Only two type values exist: info (default, inverted surface) and error (error-inverted). There is no success/warning kind. Map domain statuses: queued|running|done → type: 'info' (auto-hide true, 5000ms default), error (including per-Block output.error) → type: 'error' (auto-hide false by default, set isAutoHide:false or true with longer duration explicitly). Do not invent kind or message props.
Mount: Ensure <ToastViewport> (or <LayerProvider>) is present inside <Theme>. Currently frontend/src/main.tsx renders <Theme theme={mlblockTheme}> without ToastViewport. Add either <LayerProvider> wrap or explicit <ToastViewport position="topEnd"> as child of Theme so toasts are portalled correctly. Fallback viewport works but emits warnOnce('toast-fallback-viewport') — avoid fallback in prod.
Throttle: one toast per jobStatus transition (not per log line), use uniqueID: 'job-status' with collisionBehavior: 'overwrite' (default) to overwrite previous status toast. Per-Block errors: one toast per error, deduplicate by block_id.
Styling — Astryx-compliant:
Keep React 19 + Tailwind v4 + Astryx. Keep Theme provider as import { Theme } from '@astryxdesign/core/theme' with mlblockTheme (defineTheme({ name:'mlblock', extends: neutralTheme, tokens: {...} })) — do not remove mlblockTheme or revert to @astryxdesign/theme-neutral directly; layer order stays first line of index.css: @layer reset, theme, base, astryx-base, astryx-theme, components, utilities; (verified via styling docs bridge).
Do not add hardcoded hex/theme.color.* for Astryx containers inside RightSidebar/Journal — use Astryx semantic tokens (var(--color-background-surface), var(--color-text-secondary), var(--color-border)) or Tailwind bridge utilities (bg-surface, text-secondary, border-border, rounded-container, p-4). Existing theme.ts custom theme.color.* is legacy for BlockNode/FlowCanvas canvas colors — do not spread it to new Astryx panels.
RightSidebar/Journal shell: use Astryx primitives Card/VStack/HStack/Text/Heading/Button/Divider exclusively — no raw div layout for the shell. Pass xstyle only via stylex.create() if component-specific overrides are needed; :hover must be @media (hover:hover). Page/canvas wrappers may use Tailwind className="flex gap-3 ..." via tailwind-theme.css bridge.
Add @stylexjs/rollup-plugin (Vite) only if first xstyle is introduced — not otherwise (Astryx components ship pre-compiled).
Interfaces modified (precise props):
ToggleButtonGroup — discriminated union (per dist/ToggleButtonGroup.d.ts):
// single-select (STRICT)<ToggleButtonGrouptype="single"label="Modes du panneau"value={mode}onChange={(v: string|null)=>v&&setMode(vasMode)}size="sm"><ToggleButtonlabel="Inspecteur"value="inspecteur"/><ToggleButtonlabel="Journal"value="journal"/><ToggleButtonlabel="Cours"value="cours"/></ToggleButtonGroup>// multi: type="multiple" value: string[] — NOT used here
Note: onChange receives string | null (deselect yields null when clicking active button). Guard against null — do not store null as mode. ToggleButton takes label: string + value: string (not value as boolean). size accepts ButtonSize (sm|md|lg).
ToggleButtonGroup for Journal filter: filter: 'logs'|'outputs'|'mixte' → value={filter} onChange={v => v && setFilter(v as Filter)} with <ToggleButton label="Logs" value="logs"/> etc. Keep label prop required for a11y.
RightSidebarmode: 'inspecteur'|'journal'|'cours' defaults to 'inspecteur'. No new backend API; reuse GET /api/pipelines/{id}/jobs and GET /api/jobs/{id}/outputs. No new public interface beyond RightSidebar mode.
Card/VStack/HStack/Text/Heading/Button/Divider — use as per existing FlowCanvas.tsx pattern (import { Card, VStack, ... } from '@astryxdesign/core' and import { Text, Heading } from '@astryxdesign/core/Text').
State:useAppStore stays single truth for flowNodes/flowEdges, pipelineId, jobOutputs/lastJobId. New local state rightMode (inspecteur|journal|cours) lives in RightSidebar (or lifted to useAppStore if persistence across reload is desired). selectedJobId in JournalPanel (useState<string|null>). No new global store for logs. selectedBlockId is ReactFlow selection — keep ReactFlow-driven, don't duplicate in store.
Build discipline: One seam per commit, verified by npm run build + npm test + visual npm run dev on http://localhost:5173/editor?pipeline=... (authenticated), check no page scroll with Journal open and that ?pipeline= survives refresh (previous fix 8e30179 stays). npm run lint -- --max-warnings 0 stays green.
Testing Decisions
What makes a good test: Test external behavior, not implementation. For this spec this means: Pipeline id stays in URL after refresh, Journal lists past Jobs, fused timeline shows logs and outputs, slider filters correctly, Restaurer restores graph, auto-switch selects last Block, Toast appears on status/error – not which CSS class or which div holds the list.
Seams (highest possible, fewest): Single primary seam is the rendered Editor – npm run build (type + bundling) plus visual/manual verification via npm run dev against VITE_API_BASE_URL=http://localhost:8000 and existing vitest store/utils suite. No new backend seam. Component seams are RightSidebar mode switch, JournalPanel fused view, and Toast – tested through the composed Editor, not isolated unit tests. Ideal is one seam: the Editor the learner sees.
Which modules will be tested:
RightSidebar mode switch (inspecteur|journal|cours) – visual check, no page scroll. Verify ToggleButtonGroup has aria-label via label prop and value null-guard.
JournalPanel – list Jobs by timestamp, select, fused view, slider Logs|Outputs|Mixte, Restaurer. Verify useQuery enabled guards and fused sort a.created_at vs b.created_at.
Auto-switch on onRun – start a PipelineJob, assert useToast({body, type:'info', uniqueID:'job-status'}) called and sidebar is inspecteur and last Block selected (nodes.filter(n=>!edges.some(e=>e.source===n.id)) tail).
Toast – start Job, fail a Block, assert type:'error' toast appears, no per-line log spam. Verify ToastViewport mounted inside Theme.
Regression: npm run build + npm test (53 tests) + npm run lint -- --max-warnings 0 stay green – same as CI.
Prior art: Frontend tests are vitest run node env (store/*.test.ts, utils/*.test.ts); backend ruff check + pytest. No component snapshot/e2e harness – do not introduce one for this spec; rely on build + vitest + manual dev-server verification, matching prior Astryx migrations.
Out of Scope
Backend Catalog/Validation/Codegen/Execution – no API or schema change.
Time-travel of Catalog (Block definitions ) – restore only restores Pipelinenodes/edges, not Block code.
Full AppShell page-layout refactor beyond the right sidebar.
New e2e harness (Playwright/Cypress) or Storybook.
Search/filter beyond Logs|Outputs|Mixte slider.
Persistence of rightMode across reload (nice-to-have, not required).
Further Notes
Provenance: Grilling session 2026-08-28 identified duplication between bottom shell and right Inspector; decision to delete ConsolePanel and promote right sidebar to three modes with Journal as history reader, fused view with slider, plus restore. Previous overflow fix (min-height → height) was reverted because it did not address the seam duplication.
Problem Statement
As a learner who builds a Pipeline (DAG of Blocks from the Catalog) and runs it as a Job, the current Editor has two places that show execution information: the bottom ConsolePanel (shell) and the right Inspector. The shell makes the page taller than the viewport, so the learner must scroll to see it. The shell and the Inspector duplicate the same information (global logs vs per-Block outputs). The learner cannot easily review past Jobs of the same Pipeline; the shell only shows the current Job.
Solution
Remove the bottom shell completely and centralise execution information in the right sidebar, complemented by transient Toast notifications:
ToggleButtonGroup type="single": Inspecteur | Journal | Cours. Cours stays as is. Inspecteur stays per-Block. Journal is new.createdAt DESC, labelled by time (HH:mm, e.g.12h04,12h10). Click on a timestamp selects that Job. The view is a fused chronological timeline that interleaves globalconsoleLinesand per-Blockjob_outputscards. A slider/toggleLogs | Outputs | Mixtefilters the fused view.nodes/edgessnapshot of the selected past Job into the current Pipeline (backup / time-travel).onRun), the sidebar switches automatically to Inspecteur and selects the last topological Block (the Block with no successors) – the final output of the Pipeline.useToast+ToastViewport) shows only transient status: Job transitions (queued → running → done/error) and per-Block errors. It does not spam per-line logs. Logs stay in the Journal.User Stories
12h04,12h10), so that I can find a previous execution.Logs | Outputs | Mixtein the Journal, so that I can filter to only logs, only outputs, or both.nodes/edgessnapshot of a past Job into the current Pipeline.queued,running,done,error), so that I know the Job state without looking at the sidebar.reset,theme,base,astryx-base,astryx-theme,components,utilities) unchanged, so that the fix does not regress styling.vite buildandvitestto stay green, so that regressions are caught.Implementation Decisions
Scope is frontend-only. Catalog, Pipeline DAG, and Job execution (local / Vast.ai) stay as per
CONTEXT.md. No schema change tojobs/job_outputs; the Journal reuseslistPipelineJobs(pipelineId)andgetJobOutputs(jobId)already used inEditorPagefor the last Job. No ADR yet; this spec will not create one, but a follow-up ADR for "right sidebar as single execution seam" is recommended.Modules to modify:
frontend/src/components/ui/ConsolePanel.tsxand its usage infrontend/src/pages/EditorPage.tsx; removeconsoleLines/jobStatusrendering from the Editor shell.EditorPagelayout goes fromheader + canvas + consoletoheader + canvas(canvasflex:1 min-height:0), outerheight:100vh overflow:hiddento avoid page scroll. Remove the local customfrontend/src/components/ui/Toast.tsx(currently a customdivwiththeme.color.*hardcodes) or rename it — see Toast seam below.frontend/src/components/flow/FlowCanvas.tsxcurrently embedsInspectorPanel+CoursPanelwith localrightMode: 'cours'|'inspecteur'. Extract a newRightSidebarseam (either insideFlowCanvas.tsxor newfrontend/src/components/flow/RightSidebar.tsx) that ownsToggleButtonGroupwith three modes.FlowCanvaskeeps collapsible shell logic (rightCollapsed48/260) but the switch is the single seam.frontend/src/components/flow/JournalPanel.tsx: propspipelineId: string | null,selectedJobId: string | null,onSelectJob: (id: string) => void. Internally:useQuery(['jobs', pipelineId], () => listPipelineJobs(pipelineId), {enabled: !!pipelineId}),useQuery(['jobOutputs', selectedJobId], () => getJobOutputs(selectedJobId), {enabled: !!selectedJobId}), and deriveconsoleLinesfrom selected Job payload if available else empty. Fused timeline: mergeconsoleLines(global, ordered) withoutputsper Block bycreatedAt(new Date(o.created_at).getTime()); render asVStack gap={2}withCard variant="muted" padding={2}for outputs andTextfor logs. Timestamps formatHH:mmviatoLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'})→12h04.JournalPanel,<Button label="Restaurer cette version" variant="primary" onClick={handleRestore}>callsuseAppStore.getState().commitUndoPoint()thenloadPipeline(snapshot.nodes, snapshot.edges, snapshot.pipelineId, snapshot.name)wheresnapshotis theJob's storednodes/edgesif the API returns it, else fallback togetPipeline(selectedJob.pipelineId)fetch. After restore, optionalsetMode('inspecteur'). Snapshot comes from persisted pipeline graph at job creation time; restoring overwrites currentflowNodes/flowEdges(explicit user action, undoable via undo stack).frontend/src/hooks/useBlockRunner.tsonRunsuccess (onSuccessofrunMutation) or inFlowCanvas/RightSidebaruseEffectwatchingjobStatus === 'running', callsetMode('inspecteur')andsetSelectedBlockId(lastTopoNodeId).lastTopoNodeIdcomputed via scannodes.filter(n => !edges.some(e => e.source === n.id))and take last (Kahn topo tail). Reuse if already available. Selection is done viauseReactFlow().setNodesor store-drivenselectedflag — keep existingBlockNodeselection mechanism (ReactFlowselected).frontend/src/components/ui/Toast.tsxis not Astryx-compliant: it renders a customdivwithposition:fixed,theme.color.*hardcodes, and a customuseAppStoretoast. Delete or replace it.@astryxdesign/core0.4.6—npx @astryxdesign/cli docs styling+dist/Toast/types.d.ts):import { useToast, ToastViewport } from '@astryxdesign/core'(re-exported fromcore/Toast);const showToast = useToast()returnsShowToastFn; signatureshowToast({ body: ReactNode, type?: 'info' | 'error', isAutoHide?: boolean, autoHideDuration?: number, uniqueID?: string }). Only twotypevalues exist:info(default, inverted surface) anderror(error-inverted). There is nosuccess/warningkind. Map domain statuses:queued|running|done→type: 'info'(auto-hide true, 5000ms default),error(including per-Blockoutput.error) →type: 'error'(auto-hide false by default, setisAutoHide:falseortruewith longer duration explicitly). Do not inventkindormessageprops.showToast({ body: 'Pipeline #12 en cours…', type: 'info', uniqueID: 'job-status' }),showToast({ body: 'Échec — voir Journal', type: 'error' }).<ToastViewport>(or<LayerProvider>) is present inside<Theme>. Currentlyfrontend/src/main.tsxrenders<Theme theme={mlblockTheme}>withoutToastViewport. Add either<LayerProvider>wrap or explicit<ToastViewport position="topEnd">as child ofThemeso toasts are portalled correctly. Fallback viewport works but emitswarnOnce('toast-fallback-viewport')— avoid fallback in prod.jobStatustransition (not per log line), useuniqueID: 'job-status'withcollisionBehavior: 'overwrite'(default) to overwrite previous status toast. Per-Block errors: one toast per error, deduplicate byblock_id.React 19+Tailwind v4+ Astryx. KeepThemeprovider asimport { Theme } from '@astryxdesign/core/theme'withmlblockTheme(defineTheme({ name:'mlblock', extends: neutralTheme, tokens: {...} })) — do not removemlblockThemeor revert to@astryxdesign/theme-neutraldirectly; layer order stays first line ofindex.css:@layer reset, theme, base, astryx-base, astryx-theme, components, utilities;(verified viastylingdocs bridge).theme.color.*for Astryx containers inside RightSidebar/Journal — use Astryx semantic tokens (var(--color-background-surface),var(--color-text-secondary),var(--color-border)) or Tailwind bridge utilities (bg-surface,text-secondary,border-border,rounded-container,p-4). Existingtheme.tscustomtheme.color.*is legacy forBlockNode/FlowCanvascanvas colors — do not spread it to new Astryx panels.Card/VStack/HStack/Text/Heading/Button/Dividerexclusively — no rawdivlayout for the shell. Passxstyleonly viastylex.create()if component-specific overrides are needed;:hovermust be@media (hover:hover). Page/canvas wrappers may use TailwindclassName="flex gap-3 ..."viatailwind-theme.cssbridge.@stylexjs/rollup-plugin(Vite) only if firstxstyleis introduced — not otherwise (Astryx components ship pre-compiled).Interfaces modified (precise props):
ToggleButtonGroup— discriminated union (perdist/ToggleButtonGroup.d.ts):onChangereceivesstring | null(deselect yieldsnullwhen clicking active button). Guard againstnull— do not storenullas mode.ToggleButtontakeslabel: string+value: string(notvalueas boolean).sizeacceptsButtonSize(sm|md|lg).ToggleButtonGroupfor Journal filter:filter: 'logs'|'outputs'|'mixte'→value={filter} onChange={v => v && setFilter(v as Filter)}with<ToggleButton label="Logs" value="logs"/>etc. Keeplabelprop required for a11y.useToast(): ShowToastFn—showToast({ body, type: 'info'|'error', isAutoHide, autoHideDuration, uniqueID }). Nokind.RightSidebarmode: 'inspecteur'|'journal'|'cours'defaults to'inspecteur'. No new backend API; reuseGET /api/pipelines/{id}/jobsandGET /api/jobs/{id}/outputs. No new public interface beyondRightSidebarmode.Card/VStack/HStack/Text/Heading/Button/Divider— use as per existingFlowCanvas.tsxpattern (import { Card, VStack, ... } from '@astryxdesign/core'andimport { Text, Heading } from '@astryxdesign/core/Text').State:
useAppStorestays single truth forflowNodes/flowEdges,pipelineId,jobOutputs/lastJobId. New local staterightMode(inspecteur|journal|cours) lives inRightSidebar(or lifted touseAppStoreif persistence across reload is desired).selectedJobIdinJournalPanel(useState<string|null>). No new global store for logs.selectedBlockIdis ReactFlow selection — keep ReactFlow-driven, don't duplicate in store.Build discipline: One seam per commit, verified by
npm run build+npm test+ visualnpm run devonhttp://localhost:5173/editor?pipeline=...(authenticated), check no page scroll with Journal open and that?pipeline=survives refresh (previous fix8e30179stays).npm run lint -- --max-warnings 0stays green.Testing Decisions
npm run build(type + bundling) plus visual/manual verification vianpm run devagainstVITE_API_BASE_URL=http://localhost:8000and existingviteststore/utils suite. No new backend seam. Component seams areRightSidebarmode switch,JournalPanelfused view, andToast– tested through the composed Editor, not isolated unit tests. Ideal is one seam: the Editor the learner sees.RightSidebarmode switch (inspecteur|journal|cours) – visual check, no page scroll. VerifyToggleButtonGrouphasaria-labelvialabelprop andvaluenull-guard.JournalPanel– list Jobs by timestamp, select, fused view, sliderLogs|Outputs|Mixte, Restaurer. VerifyuseQueryenabled guards and fused sorta.created_at vs b.created_at.onRun– start a Pipeline Job, assertuseToast({body, type:'info', uniqueID:'job-status'})called and sidebar isinspecteurand last Block selected (nodes.filter(n=>!edges.some(e=>e.source===n.id))tail).Toast– start Job, fail a Block, asserttype:'error'toast appears, no per-line log spam. VerifyToastViewportmounted insideTheme.npm run build+npm test(53 tests) +npm run lint -- --max-warnings 0stay green – same as CI.vitest runnode env (store/*.test.ts,utils/*.test.ts); backendruff check+pytest. No component snapshot/e2e harness – do not introduce one for this spec; rely on build + vitest + manual dev-server verification, matching prior Astryx migrations.Out of Scope
nodes/edges, not Block code.AppShellpage-layout refactor beyond the right sidebar.Logs|Outputs|Mixteslider.rightModeacross reload (nice-to-have, not required).Further Notes
ConsolePaneland promote right sidebar to three modes with Journal as history reader, fused view with slider, plus restore. Previous overflow fix (min-height → height) was reverted because it did not address the seam duplication.npx @astryxdesign/cli docs(styling,layout,theme,tokens,color,spacing):ToggleButtonGrouptype="single"is discriminatedvalue: string|null → onChange(string|null)— added null-guard.ToastAPI isuseToast(): ShowToastFnwithToastOptions {body, type:'info'|'error'}— spec previously inventedkind/message/success— corrected tobody/type.ToastViewport/LayerProvidermount requirement added (fallback warns).Card/VStack/HStack/Text/Headingcomposition,@layerorder,Tailwind bridge(tailwind-theme.css),xstyle={stylex.create()}with@media (hover:hover),color/spacingtokens verified.ConsolePanel.tsx, customToast.tsx), preciseRightSidebar.tsxvsFlowCanvas.tsxsplit,JournalPanel.tsxquery guards. Token usage clarified: Astryx semanticvar(--color-*)/ Tailwind utilities for new panels, not legacytheme.color.*hardcodes. Accessibility:ToggleButtonGrouplabelisaria-label,ToastViewportposition.