diff --git a/src/learn/SpecProgression.module.css b/src/learn/SpecProgression.module.css index 0c922a4..82eb8e2 100644 --- a/src/learn/SpecProgression.module.css +++ b/src/learn/SpecProgression.module.css @@ -102,6 +102,19 @@ min-height: 180px; } +/* The per-stage hand-off into the app: the stage's spec, self-contained, as a + snippet — opened in a new tab so the lesson keeps its place. */ +.openLink { + display: inline-block; + margin-top: var(--space-3); + font-size: 13px; + color: var(--accent); + text-decoration: none; +} +.openLink:hover { + text-decoration: underline; +} + .note { padding: var(--space-4) var(--space-5); border-top: 1px solid var(--border); diff --git a/src/learn/SpecProgression.tsx b/src/learn/SpecProgression.tsx index 9cf1454..a9a6cc7 100644 --- a/src/learn/SpecProgression.tsx +++ b/src/learn/SpecProgression.tsx @@ -2,6 +2,7 @@ import { useId, useMemo, useRef, useState, type KeyboardEvent, type ReactNode } import type { Config } from 'vega-lite'; import { injectDatasets, type ProgressionStage } from '@core/lesson-parse'; import { formatSpec } from '@core/json-format'; +import { specLinkHref } from '@core/spec-link'; import { changedLines } from '@core/spec-diff'; import { LandingChart } from '../landing/LandingChart'; import { Markdown } from './Markdown'; @@ -66,6 +67,14 @@ export function SpecProgression({ const activeStage = stages[active]; + // Hand-off into the app (spec §01E → one-shot action links): the link carries + // the *injected* spec — the app has no lesson datasets, so the snippet must + // arrive self-contained even though the source pane shows data by name. + const openHref = useMemo( + () => specLinkHref(formatSpec(injectDatasets(activeStage.spec, datasets))), + [activeStage, datasets], + ); + return (