Skip to content

Spec: Right sidebar 3 modes (Inspecteur/Journal/Cours) — delete ConsolePanel, centralise execution via Journal + Toast #7

Description

@Ouaziz-chedli

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:

  • 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-Block job_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

  1. 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.
  2. As a learner, I want no bottom shell, so that there is one place for execution information, not two.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. 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.
  10. 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.
  11. 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.
  12. As a learner, I want Toast to show per-Block errors, so that I see failures immediately even if the Journal is not open.
  13. As a learner, I want Toast not to spam per-line logs, so that I am not overwhelmed.
  14. 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.
  15. As a new contributor, I want the ConsolePanel deleted, so that there is one seam for execution history (the Journal) and not two.
  16. 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.
  17. 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.ts onRun success (onSuccess of runMutation) or in FlowCanvas/RightSidebar useEffect 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/core 0.4.6npx @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|donetype: '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.
      • Example: showToast({ body: 'Pipeline #12 en cours…', type: 'info', uniqueID: 'job-status' }), showToast({ body: 'Échec — voir Journal', type: 'error' }).
      • 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)
      <ToggleButtonGroup type="single" label="Modes du panneau" value={mode} onChange={(v: string | null) => v && setMode(v as Mode)} size="sm">
        <ToggleButton label="Inspecteur" value="inspecteur" />
        <ToggleButton label="Journal" value="journal" />
        <ToggleButton label="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.
    • useToast(): ShowToastFnshowToast({ body, type: 'info'|'error', isAutoHide, autoHideDuration, uniqueID }). No kind.
    • RightSidebar mode: '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 Editornpm 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 Pipeline Job, 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 Pipeline nodes/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.
  • Seam confirmation: Single rendered-Editor seam assumed as per prior specs (e.g. Spec: Complete Astryx theme/token integration and finish primitive migration #5). If a stricter visual seam (Chromatic/Playwright) is desired, it is a follow-up spec.
  • Follow-up: After landing, propose ADR "Right sidebar as single execution seam (Journal replaces ConsolePanel)".
  • Audit 2026-08-27 — Astryx compliance review (spec already refined, no intent change):
    • Verified via npx @astryxdesign/cli docs (styling, layout, theme, tokens, color, spacing): ToggleButtonGroup type="single" is discriminated value: string|null → onChange(string|null) — added null-guard. Toast API is useToast(): ShowToastFn with ToastOptions {body, type:'info'|'error'} — spec previously invented kind/message/successcorrected to body/type. ToastViewport/LayerProvider mount requirement added (fallback warns). Card/VStack/HStack/Text/Heading composition, @layer order, Tailwind bridge (tailwind-theme.css), xstyle={stylex.create()} with @media (hover:hover), color/spacing tokens verified.
    • File seams tightened: explicit delete list (ConsolePanel.tsx, custom Toast.tsx), precise RightSidebar.tsx vs FlowCanvas.tsx split, JournalPanel.tsx query guards. Token usage clarified: Astryx semantic var(--color-*) / Tailwind utilities for new panels, not legacy theme.color.* hardcodes. Accessibility: ToggleButtonGroup label is aria-label, ToastViewport position.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentFully specified, ready for an AFK agent

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions