@lincyaw/addon-exec 0.0.0-stage → 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +202 -0
  2. package/LICENSES +696 -0
  3. package/README.md +33 -2
  4. package/components/PmRunPanel.css +167 -0
  5. package/components/PmRunPanel.tsx +389 -0
  6. package/components/PmStepRun.css +61 -0
  7. package/components/PmStepRun.tsx +202 -0
  8. package/package.json +50 -3
  9. package/src/client/RunOutputs.css +21 -0
  10. package/src/client/RunOutputs.tsx +69 -0
  11. package/src/client/StepMark.css +3 -0
  12. package/src/client/StepMark.tsx +12 -0
  13. package/src/client/env.d.ts +13 -0
  14. package/src/client/feedback.ts +69 -0
  15. package/src/client/format.ts +30 -0
  16. package/src/client/index.ts +14 -0
  17. package/src/client/messages.ts +102 -0
  18. package/src/client/outputs.ts +29 -0
  19. package/src/client/plugin.ts +55 -0
  20. package/src/client/pyodide/executor.ts +94 -0
  21. package/src/client/pyodide/presmium_gpu.py +297 -0
  22. package/src/client/pyodide/protocol.ts +27 -0
  23. package/src/client/pyodide/pyodide.worker.ts +68 -0
  24. package/src/client/pyodide/run_await.py +57 -0
  25. package/src/client/runs-in.ts +27 -0
  26. package/src/client/script/executor.ts +62 -0
  27. package/src/client/script/script.worker.ts +9 -0
  28. package/src/client/slide-fit.ts +61 -0
  29. package/src/client/step-marks.ts +7 -0
  30. package/src/client/when-service.ts +14 -0
  31. package/src/domain/browser-python.ts +33 -0
  32. package/src/domain/config.ts +41 -0
  33. package/src/domain/index.ts +11 -0
  34. package/src/domain/outputs.ts +18 -0
  35. package/src/domain/router.ts +50 -0
  36. package/src/domain/runs.ts +72 -0
  37. package/src/domain/static-output.ts +20 -0
  38. package/src/domain/trace.ts +32 -0
  39. package/src/node/browser-imports.ts +34 -0
  40. package/src/node/child-process.ts +109 -0
  41. package/src/node/code-component.ts +48 -0
  42. package/src/node/notebook-cells.ts +46 -0
  43. package/src/node/presmium.ts +27 -0
  44. package/src/node/pyodide.ts +22 -0
  45. package/src/node/python.ts +40 -0
  46. package/src/node/run-panel.ts +71 -0
  47. package/src/node/script-main.mjs +11 -0
  48. package/src/node/script.ts +26 -0
  49. package/src/node/step-run.ts +71 -0
  50. package/src/node/timeouts.ts +15 -0
  51. package/src/shared/script-runner.d.mts +16 -0
  52. package/src/shared/script-runner.mjs +168 -0
package/README.md CHANGED
@@ -1,3 +1,34 @@
1
- # Temporary Holding Version
1
+ # @lincyaw/addon-exec
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Running code: run panels, step-by-step execution and recorded outputs; Python in the browser with Pyodide, offline too.
4
+
5
+ Part of [Presmium](https://github.com/Lincyaw/presmium), which compiles canonical lecture.json teaching documents into web pages, slides, Jupyter notebooks and narrated playback.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ pnpm add -D @lincyaw/addon-exec
11
+ ```
12
+
13
+ Add this package to `metadata.plugins` in the document's `lecture.json`.
14
+
15
+ It is a Presmium plugin: its components work on the web page and in the slides. It needs `@lincyaw/cli` and the renderers in the same project, and declares the Presmium packages it shares with them as peer dependencies (Presmium 1.x).
16
+
17
+ ## What it provides
18
+
19
+ - Run-panel components with a canonical child code block, structured parameters and rich execution outputs. The compiler derives displayed code from that child, including referenced project code.
20
+ - Step-run components for Python execution traces.
21
+ - Run controls for code blocks whose `execution.when` is `live` or `both`, through the configured real executor.
22
+
23
+ ## Configuration
24
+
25
+ `presmium.exec.timeoutMs`: the timeout of runs (with section overrides in `sections[id].config`).
26
+
27
+ ## Documentation
28
+
29
+ - [Author guide](https://github.com/Lincyaw/presmium/blob/main/site/guide/locales/en/lecture.json) (Chinese: `site/guide/lecture.json`)
30
+ - [Document contract](https://github.com/Lincyaw/presmium/blob/main/.agents/skills/presmium-author/references/document.md): normalized entities, parameters, states and shared validation
31
+
32
+ ## License
33
+
34
+ Apache-2.0
@@ -0,0 +1,167 @@
1
+ .pm-run-panel {
2
+ --pm-text-sm: 0.875em;
3
+ --pm-text-md: 1em;
4
+
5
+ display: grid;
6
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 26rem), 1fr));
7
+ gap: 0.75em 1.25em;
8
+ color: var(--pm-color-ink);
9
+ font-family: var(--pm-font-sans);
10
+ font-size: 0.8em;
11
+ }
12
+ .pm-run-panel__main,
13
+ .pm-run-panel__side {
14
+ min-width: 0;
15
+ }
16
+ .pm-run-panel__main {
17
+ overflow: hidden;
18
+ border: var(--pm-size-hairline) solid var(--pm-color-line);
19
+ border-radius: var(--pm-radius-panel);
20
+ background: var(--pm-color-surface);
21
+ }
22
+ .pm-run-panel__bar {
23
+ display: flex;
24
+ flex-wrap: wrap;
25
+ align-items: center;
26
+ gap: 0.4em 0.75em;
27
+ padding: 0.45em 0.6em 0.45em 1.1em;
28
+ border-bottom: var(--pm-size-hairline) solid var(--pm-color-line);
29
+ color: var(--pm-color-muted);
30
+ }
31
+ .pm-run-panel__lang {
32
+ font-family: var(--pm-font-mono);
33
+ }
34
+ .pm-run-panel__controls {
35
+ display: flex;
36
+ flex: 1 1 auto;
37
+ flex-wrap: wrap;
38
+ align-items: center;
39
+ justify-content: flex-end;
40
+ gap: 0.4em 0.75em;
41
+ min-width: 0;
42
+ }
43
+ .pm-run-panel__source {
44
+ font-size: 1.25em;
45
+ }
46
+ .pm-run-panel__source .pm-code-block {
47
+ margin: 0;
48
+ }
49
+ .pm-run-panel__source pre {
50
+ margin: 0;
51
+ border: 0;
52
+ border-radius: 0;
53
+ max-height: 20em;
54
+ overflow: auto;
55
+ }
56
+ .pm-run-panel--present {
57
+ align-items: start;
58
+ }
59
+ .pm-run-panel--present .pm-run-panel__source pre {
60
+ max-height: max(var(--pm-run-code-space, 20em), 6em);
61
+ }
62
+ .pm-run-panel--present .pm-run-panel__side {
63
+ max-height: var(--pm-run-panel-space);
64
+ overflow: auto;
65
+ }
66
+ .pm-run-panel--present .pm-run-panel__source :is(pre, code) {
67
+ white-space: pre-wrap;
68
+ overflow-wrap: anywhere;
69
+ }
70
+ .pm-run-panel--present .pm-run-panel__bar {
71
+ font-size: 1.25em;
72
+ }
73
+ .pm-run-panel--present .pm-run-panel__where {
74
+ font-size: 0.8em;
75
+ }
76
+ .pm-run-panel__bar :is(input, button) {
77
+ font-size: inherit;
78
+ }
79
+ .pm-run-panel__param {
80
+ display: inline-flex;
81
+ align-items: center;
82
+ gap: 0.4em;
83
+ max-width: 100%;
84
+ }
85
+ .pm-run-panel__param code {
86
+ color: var(--pm-color-ink);
87
+ }
88
+ .pm-run-panel__param input {
89
+ min-width: 7.5em;
90
+ max-width: 100%;
91
+ min-height: 0;
92
+ padding: 0.2em 0.5em;
93
+ font-family: var(--pm-font-mono);
94
+ }
95
+ .pm-run-panel__run {
96
+ min-height: 0;
97
+ padding: 0.3em 1em;
98
+ }
99
+ .pm-run-panel__hint {
100
+ color: var(--pm-color-danger);
101
+ }
102
+ .pm-run-panel__side {
103
+ display: flex;
104
+ flex-direction: column;
105
+ gap: 0.5em;
106
+ }
107
+ .pm-run-panel__output {
108
+ padding: 0.75em 1.1em;
109
+ border-radius: var(--pm-radius-panel);
110
+ background: var(--pm-color-sunk);
111
+ }
112
+ .pm-run-panel__label {
113
+ margin-bottom: 0.4em;
114
+ color: var(--pm-color-muted);
115
+ font-size: 0.9em;
116
+ }
117
+ .pm-run-panel__side pre {
118
+ margin: 0;
119
+ max-height: 8em;
120
+ overflow: auto;
121
+ font-family: var(--pm-font-mono);
122
+ font-size: 1.1em;
123
+ line-height: 1.6;
124
+ white-space: pre-wrap;
125
+ }
126
+ .pm-run-panel__badge {
127
+ margin-bottom: 0.5em;
128
+ white-space: normal;
129
+ }
130
+ .pm-run-panel__stderr {
131
+ color: var(--pm-color-danger);
132
+ }
133
+ .pm-run-panel__history {
134
+ margin: 0;
135
+ padding: 0;
136
+ list-style: none;
137
+ max-height: 6.5em;
138
+ overflow: auto;
139
+ color: var(--pm-color-muted);
140
+ }
141
+ .pm-run-panel__history label {
142
+ display: inline-flex;
143
+ align-items: center;
144
+ gap: 0.4em;
145
+ }
146
+ .pm-run-panel__compare {
147
+ padding: 0.6em 0.9em;
148
+ border: var(--pm-size-hairline) solid var(--pm-color-line);
149
+ border-radius: var(--pm-radius-control);
150
+ }
151
+ .pm-run-panel__compare-title {
152
+ font-weight: 600;
153
+ }
154
+ .pm-run-panel__side .pm-run-panel__diff {
155
+ max-height: 11em;
156
+ font-size: 0.95em;
157
+ }
158
+ .pm-run-panel__diff span {
159
+ display: block;
160
+ }
161
+ .pm-run-panel__diff span[data-kind='added'] {
162
+ color: var(--pm-color-add);
163
+ }
164
+ .pm-run-panel__diff span[data-kind='removed'] {
165
+ color: var(--pm-color-del);
166
+ text-decoration: line-through;
167
+ }
@@ -0,0 +1,389 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+ import type { CSSProperties, ReactNode } from 'react'
3
+ import {
4
+ PmIcon,
5
+ useMessages,
6
+ usePresentationMode,
7
+ useSectionConfig,
8
+ useService,
9
+ useStartReport,
10
+ } from '@lincyaw/host'
11
+ import { EXECUTOR, isRecord } from '@lincyaw/kernel'
12
+ import {
13
+ languageName,
14
+ MESSAGES,
15
+ runsInOf,
16
+ seconds,
17
+ useSlideSpaceBelow,
18
+ } from '../src/client/index.ts'
19
+ import {
20
+ appendRun,
21
+ compareRuns,
22
+ configuredTimeout,
23
+ defaultPair,
24
+ EXEC_ID,
25
+ fallbackReason,
26
+ stringArgs,
27
+ withRecordedFallback,
28
+ } from '../src/domain/index.ts'
29
+ import type { RunHistory, RunRecord } from '../src/domain/index.ts'
30
+ import RunOutputs from '../src/client/RunOutputs.tsx'
31
+ import './PmRunPanel.css'
32
+ interface Props {
33
+ lang?: string
34
+ code?: string
35
+ params?: Record<string, unknown>
36
+ recorded?: string
37
+ compiledOutput?: string
38
+ timeout?: number
39
+ executor?: string
40
+ session?: string
41
+ place?: { section: string; block: string }
42
+ children?: ReactNode
43
+ }
44
+ export default function PmRunPanel(props: Props) {
45
+ const { lang = 'python', code = '', children } = props
46
+ const executor = useService(EXECUTOR),
47
+ report = useStartReport(),
48
+ config = useSectionConfig(EXEC_ID),
49
+ t = useMessages(MESSAGES)
50
+ const [args, setArgs] = useState<Record<string, string>>(() =>
51
+ stringArgs(props.params),
52
+ )
53
+ const [fields, setFields] = useState<Record<string, Record<string, string>>>(
54
+ () =>
55
+ Object.fromEntries(
56
+ Object.entries(props.params ?? {})
57
+ .filter(([, v]) => isRecord(v))
58
+ .map(([name, v]) => [name, stringArgs(v as Record<string, unknown>)]),
59
+ ),
60
+ )
61
+ const presenting = usePresentationMode() === 'slides',
62
+ root = useRef<HTMLDivElement>(null)
63
+ const [space, setSpace] = useState<{ panel: number; code: number }>()
64
+ useSlideSpaceBelow(root, presenting, (panel) => {
65
+ const main = root.current?.querySelector<HTMLElement>(
66
+ '.pm-run-panel__main',
67
+ ),
68
+ pre = main?.querySelector<HTMLElement>('.pm-run-panel__source pre')
69
+ if (main && pre) {
70
+ const code = panel - (main.offsetHeight - pre.offsetHeight)
71
+ setSpace((old) =>
72
+ old?.panel === panel && old.code === code ? old : { panel, code },
73
+ )
74
+ }
75
+ })
76
+ const [history, setHistory] = useState<RunHistory>([]),
77
+ [running, setRunning] = useState(false),
78
+ [resetting, setResetting] = useState(false),
79
+ [selected, setSelected] = useState<number[]>([]),
80
+ [failure, setFailure] = useState<string>()
81
+ const current = useRef<AbortController>()
82
+ useEffect(() => () => current.current?.abort(), [])
83
+ const executors = executor?.list() ?? [],
84
+ routed = props.executor
85
+ ? executors.find((e) => e.id === props.executor)
86
+ : executors.find((e) => e.supports(lang))
87
+ const display = routed?.params?.(lang),
88
+ latest = history.at(-1)
89
+ const executionId = latest?.result.executorId ?? props.executor ?? routed?.id
90
+ const where = runsInOf(
91
+ executionId,
92
+ executionId === routed?.id
93
+ ? routed
94
+ : executors.find((e) => e.id === executionId),
95
+ )
96
+ const runsInText = where
97
+ ? where.kind === 'browser' || where.kind === 'machine'
98
+ ? t.runsIn[where.kind]
99
+ : t.runsIn[where.kind](where.name)
100
+ : undefined
101
+ const label = (name: string) => display?.labels[name] ?? name
102
+ const split = (name: string) =>
103
+ Boolean(display?.fields?.includes(name) && fields[name])
104
+ const runArgs = (): Record<string, string> =>
105
+ Object.fromEntries(
106
+ Object.entries(args).map(([name, v]) => [
107
+ name,
108
+ split(name)
109
+ ? `{${Object.entries(fields[name]!)
110
+ .map(
111
+ ([key, field]) =>
112
+ `${JSON.stringify(key)}: ${field.trim() || 'null'}`,
113
+ )
114
+ .join(', ')}}`
115
+ : v,
116
+ ]),
117
+ )
118
+ const why = latest && fallbackReason(latest.result)
119
+ const reason = why
120
+ ? why === 'failed' &&
121
+ latest.result.exitCode === null &&
122
+ latest.result.stderr.trim()
123
+ ? latest.result.stderr.trim()
124
+ : t[why]
125
+ : undefined
126
+ const outputLabel =
127
+ latest?.result.source === 'live'
128
+ ? t.outputTook(seconds(latest.result.durationMs))
129
+ : t.output
130
+ const [before, after] = [...selected]
131
+ .sort((a, b) => a - b)
132
+ .map((id) => history.find((r) => r.id === id))
133
+ const comparison = before && after ? compareRuns(before, after) : undefined
134
+ async function run() {
135
+ if (!executor || running || resetting) return
136
+ const controller = new AbortController()
137
+ current.current = controller
138
+ setRunning(true)
139
+ setFailure(undefined)
140
+ try {
141
+ const request = {
142
+ lang,
143
+ code,
144
+ args: runArgs(),
145
+ timeoutMs: props.timeout ?? configuredTimeout(config),
146
+ executor: props.executor,
147
+ session: props.session,
148
+ place: props.place,
149
+ }
150
+ const result = await executor.run(request, controller.signal)
151
+ if (controller.signal.aborted) return
152
+ const recorded =
153
+ props.recorded ??
154
+ (!result.ok && result.exitCode === null
155
+ ? props.compiledOutput
156
+ : undefined)
157
+ const next = appendRun(
158
+ history,
159
+ request.args,
160
+ withRecordedFallback(result, recorded),
161
+ )
162
+ setHistory(next)
163
+ setSelected(defaultPair(next) ?? [])
164
+ } catch (error) {
165
+ if (!controller.signal.aborted)
166
+ setFailure(error instanceof Error ? error.message : String(error))
167
+ } finally {
168
+ if (current.current === controller) {
169
+ current.current = undefined
170
+ setRunning(false)
171
+ }
172
+ }
173
+ }
174
+ async function reset() {
175
+ if (!executor || !props.session || resetting) return
176
+ setResetting(true)
177
+ current.current?.abort()
178
+ current.current = undefined
179
+ setRunning(false)
180
+ try {
181
+ await executor.endSession?.(props.session)
182
+ setHistory([])
183
+ setSelected([])
184
+ } catch (error) {
185
+ setFailure(error instanceof Error ? error.message : String(error))
186
+ } finally {
187
+ setResetting(false)
188
+ }
189
+ }
190
+ function describe(record: RunRecord) {
191
+ const params =
192
+ display?.summary?.(record.args) ??
193
+ Object.entries(record.args)
194
+ .map(([name, v]) => `${name}=${v}`)
195
+ .join(' ')
196
+ return [
197
+ params,
198
+ record.result.source === 'recorded'
199
+ ? t.recorded
200
+ : record.result.ok
201
+ ? t.succeeded
202
+ : t.failedShort,
203
+ `${record.result.durationMs} ms`,
204
+ ]
205
+ .filter(Boolean)
206
+ .join(' · ')
207
+ }
208
+ const size = (value?: string) =>
209
+ Math.min(60, Math.max(8, (value ?? '').length + 1))
210
+ return (
211
+ <div
212
+ ref={root}
213
+ className={`pm-run-panel${presenting ? ' pm-run-panel--present' : ''}`}
214
+ style={
215
+ space
216
+ ? ({
217
+ '--pm-run-panel-space': `${space.panel}px`,
218
+ '--pm-run-code-space': `${space.code}px`,
219
+ } as CSSProperties)
220
+ : undefined
221
+ }
222
+ data-state={running ? 'running' : latest ? 'done' : 'idle'}
223
+ data-runs={history.length}
224
+ >
225
+ <div className="pm-run-panel__main">
226
+ <form
227
+ className="pm-run-panel__bar pm-run-panel__form"
228
+ onSubmit={(e) => {
229
+ e.preventDefault()
230
+ void run()
231
+ }}
232
+ >
233
+ <span className="pm-run-panel__where">
234
+ <span className="pm-run-panel__lang">{languageName(lang)}</span>
235
+ {runsInText && ` · ${runsInText}`}
236
+ </span>
237
+ <span className="pm-run-panel__controls">
238
+ {Object.keys(args).map((name) =>
239
+ split(name) ? (
240
+ Object.keys(fields[name]!).map((key) => (
241
+ <label key={`${name}.${key}`} className="pm-run-panel__param">
242
+ <span>
243
+ {label(name)} <code>{key}</code>
244
+ </span>
245
+ <input
246
+ className="pm-ui-input"
247
+ name={`${name}.${key}`}
248
+ value={fields[name]![key]}
249
+ size={size(fields[name]![key])}
250
+ spellCheck={false}
251
+ autoComplete="off"
252
+ onChange={(e) =>
253
+ setFields((old) => ({
254
+ ...old,
255
+ [name]: { ...old[name], [key]: e.target.value },
256
+ }))
257
+ }
258
+ />
259
+ </label>
260
+ ))
261
+ ) : (
262
+ <label key={name} className="pm-run-panel__param">
263
+ <span>{label(name)}</span>
264
+ <input
265
+ className="pm-ui-input"
266
+ name={name}
267
+ value={args[name]}
268
+ size={size(args[name])}
269
+ spellCheck={false}
270
+ autoComplete="off"
271
+ onChange={(e) =>
272
+ setArgs((old) => ({ ...old, [name]: e.target.value }))
273
+ }
274
+ />
275
+ </label>
276
+ ),
277
+ )}
278
+ {!executor && report && (
279
+ <span className="pm-run-panel__hint">{t.unavailable}</span>
280
+ )}
281
+ {props.session && (
282
+ <button
283
+ type="button"
284
+ className="pm-run-panel__reset pm-ui-button"
285
+ disabled={resetting || !executor}
286
+ onClick={() => void reset()}
287
+ >
288
+ {t.resetSession}
289
+ </button>
290
+ )}
291
+ <button
292
+ type="submit"
293
+ className="pm-run-panel__run pm-ui-button pm-ui-button--primary"
294
+ disabled={running || resetting || !executor}
295
+ >
296
+ <PmIcon name="play" />
297
+ {running ? t.running : history.length ? t.rerun : t.run}
298
+ </button>
299
+ </span>
300
+ </form>
301
+ <div className="pm-run-panel__source">
302
+ {children ?? (
303
+ <pre>
304
+ <code>{code}</code>
305
+ </pre>
306
+ )}
307
+ </div>
308
+ </div>
309
+ {(latest || history.length > 0 || failure) && (
310
+ <div className="pm-run-panel__side">
311
+ {failure && (
312
+ <p role="alert" className="pm-run-panel__stderr">
313
+ {failure}
314
+ </p>
315
+ )}
316
+ {latest && (
317
+ <div
318
+ className="pm-run-panel__output"
319
+ data-source={latest.result.source}
320
+ data-ok={latest.result.ok}
321
+ >
322
+ <div className="pm-run-panel__label">{outputLabel}</div>
323
+ {reason && (
324
+ <div className="pm-run-panel__badge pm-ui-badge pm-ui-badge--warn">
325
+ {t.recordedBadge(reason)}
326
+ </div>
327
+ )}
328
+ <RunOutputs result={latest.result} hideError={Boolean(reason)} />
329
+ </div>
330
+ )}
331
+ {history.length > 0 && (
332
+ <ol className="pm-run-panel__history">
333
+ {history.map((record) => (
334
+ <li key={record.id} data-run={record.id}>
335
+ <label>
336
+ <input
337
+ type="checkbox"
338
+ className="pm-ui-checkbox"
339
+ checked={selected.includes(record.id)}
340
+ onChange={() =>
341
+ setSelected((old) =>
342
+ old.includes(record.id)
343
+ ? old.filter((id) => id !== record.id)
344
+ : [...old, record.id].slice(-2),
345
+ )
346
+ }
347
+ />
348
+ #{record.id} {describe(record)}
349
+ </label>
350
+ </li>
351
+ ))}
352
+ </ol>
353
+ )}
354
+ {comparison && (
355
+ <div
356
+ className="pm-run-panel__compare"
357
+ data-changed={comparison.changed}
358
+ >
359
+ <div className="pm-run-panel__compare-title">
360
+ {t.compare(comparison.before.id, comparison.after.id)}
361
+ </div>
362
+ {comparison.args.map((change) => (
363
+ <div
364
+ key={change.name}
365
+ className="pm-run-panel__arg"
366
+ data-arg={change.name}
367
+ >
368
+ {t.argChange(
369
+ label(change.name),
370
+ change.before ?? '—',
371
+ change.after ?? '—',
372
+ )}
373
+ </div>
374
+ ))}
375
+ <pre className="pm-run-panel__diff">
376
+ {comparison.output.map((line, i) => (
377
+ <span key={i} data-kind={line.kind}>
378
+ {{ same: ' ', added: '+', removed: '-' }[line.kind]}{' '}
379
+ {line.text}
380
+ </span>
381
+ ))}
382
+ </pre>
383
+ </div>
384
+ )}
385
+ </div>
386
+ )}
387
+ </div>
388
+ )
389
+ }
@@ -0,0 +1,61 @@
1
+ .pm-step-run {
2
+ --pm-text-sm: 0.875em;
3
+
4
+ display: grid;
5
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
6
+ gap: 0.75em;
7
+ color: var(--pm-color-ink);
8
+ font-family: var(--pm-font-sans);
9
+ font-size: 0.8em;
10
+ }
11
+ .pm-step-run__source {
12
+ min-width: 0;
13
+ }
14
+ .pm-step-run__source pre {
15
+ margin: 0;
16
+ }
17
+ .pm-step-run__source .line {
18
+ display: inline-block;
19
+ min-width: 100%;
20
+ }
21
+ .pm-step-run__source .pm-step-run__line {
22
+ background: var(--pm-color-selected);
23
+ box-shadow: inset var(--pm-size-rule) 0 var(--pm-color-accent);
24
+ }
25
+ .pm-step-run__side {
26
+ min-width: 0;
27
+ }
28
+ .pm-step-run__status {
29
+ margin-bottom: 0.4em;
30
+ color: var(--pm-color-muted);
31
+ }
32
+ .pm-step-run__warning {
33
+ color: var(--pm-color-warn);
34
+ }
35
+ .pm-step-run__stepper {
36
+ display: flex;
37
+ align-items: center;
38
+ gap: 0.5em;
39
+ margin-bottom: 0.4em;
40
+ }
41
+ .pm-step-run__stepper .pm-ui-button {
42
+ min-width: 2.25em;
43
+ }
44
+ .pm-step-run__stepper input {
45
+ flex: 1;
46
+ min-width: 0;
47
+ }
48
+ .pm-step-run__output {
49
+ margin: 0;
50
+ min-height: 3em;
51
+ max-height: 18em;
52
+ overflow: auto;
53
+ padding: 0.6em 0.9em;
54
+ border-radius: var(--pm-radius-panel);
55
+ background: var(--pm-color-sunk);
56
+ color: var(--pm-color-ink);
57
+ font-family: var(--pm-font-mono);
58
+ font-size: 0.95em;
59
+ line-height: 1.6;
60
+ white-space: pre-wrap;
61
+ }