walkthru 0.0.2__tar.gz
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.
- walkthru-0.0.2/.editorconfig +17 -0
- walkthru-0.0.2/.gitattributes +1 -0
- walkthru-0.0.2/.github/workflows/ci.yml +48 -0
- walkthru-0.0.2/.github/workflows/npm-ci-ts.yml +30 -0
- walkthru-0.0.2/.gitignore +124 -0
- walkthru-0.0.2/DECISIONS.md +255 -0
- walkthru-0.0.2/LICENSE +21 -0
- walkthru-0.0.2/PKG-INFO +74 -0
- walkthru-0.0.2/PLAN.md +284 -0
- walkthru-0.0.2/README.md +57 -0
- walkthru-0.0.2/misc/docs/WALKTHRU-AGENT-BRIEF.md +297 -0
- walkthru-0.0.2/misc/docs/demo2reel-01-Tooling-Capability-Survey-A-Play-Commands-and-Record-a-Demo-System.md +299 -0
- walkthru-0.0.2/misc/docs/demo2reel-02-The-Demo-Document-A-Data-Model-and-Open-Closed-Architecture-for-Editable-Demo-Tour-Artifacts.md +292 -0
- walkthru-0.0.2/pyproject.toml +98 -0
- walkthru-0.0.2/schema/demo-document.schema.json +1189 -0
- walkthru-0.0.2/schema/fixtures/full-demo.json +350 -0
- walkthru-0.0.2/schema/fixtures/minimal-demo.json +52 -0
- walkthru-0.0.2/tests/__init__.py +0 -0
- walkthru-0.0.2/tests/builders.py +229 -0
- walkthru-0.0.2/tests/fakes.py +114 -0
- walkthru-0.0.2/tests/test_engine.py +132 -0
- walkthru-0.0.2/tests/test_export.py +101 -0
- walkthru-0.0.2/tests/test_firewall.py +41 -0
- walkthru-0.0.2/tests/test_reelee_target.py +256 -0
- walkthru-0.0.2/tests/test_schema.py +81 -0
- walkthru-0.0.2/tests/test_timeline.py +118 -0
- walkthru-0.0.2/tests/test_walkthru.py +13 -0
- walkthru-0.0.2/ts/package-lock.json +2514 -0
- walkthru-0.0.2/ts/package.json +65 -0
- walkthru-0.0.2/ts/scripts/codegen.mjs +94 -0
- walkthru-0.0.2/ts/src/index.test.ts +12 -0
- walkthru-0.0.2/ts/src/index.ts +4 -0
- walkthru-0.0.2/ts/src/schema.codegen.test.ts +28 -0
- walkthru-0.0.2/ts/src/schema.generated.ts +72 -0
- walkthru-0.0.2/ts/src/schema.roundtrip.test.ts +58 -0
- walkthru-0.0.2/ts/tsconfig.json +20 -0
- walkthru-0.0.2/walkthru/__init__.py +114 -0
- walkthru-0.0.2/walkthru/adapters/__init__.py +10 -0
- walkthru-0.0.2/walkthru/adapters/export/__init__.py +16 -0
- walkthru-0.0.2/walkthru/adapters/export/json_target.py +40 -0
- walkthru-0.0.2/walkthru/adapters/export/webvtt.py +38 -0
- walkthru-0.0.2/walkthru/core/__init__.py +116 -0
- walkthru-0.0.2/walkthru/core/engine.py +196 -0
- walkthru-0.0.2/walkthru/core/events.py +154 -0
- walkthru-0.0.2/walkthru/core/schema.py +334 -0
- walkthru-0.0.2/walkthru/core/timeline.py +177 -0
- walkthru-0.0.2/walkthru/ecosystem/__init__.py +12 -0
- walkthru-0.0.2/walkthru/ecosystem/reelee/__init__.py +37 -0
- walkthru-0.0.2/walkthru/ecosystem/reelee/render_target.py +433 -0
- walkthru-0.0.2/walkthru/ports/__init__.py +98 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
|
|
3
|
+
[*]
|
|
4
|
+
charset = utf-8
|
|
5
|
+
end_of_line = lf
|
|
6
|
+
insert_final_newline = true
|
|
7
|
+
trim_trailing_whitespace = true
|
|
8
|
+
|
|
9
|
+
[*.{py,toml,yml,yaml}]
|
|
10
|
+
indent_style = space
|
|
11
|
+
indent_size = 4
|
|
12
|
+
|
|
13
|
+
[*.md]
|
|
14
|
+
trim_trailing_whitespace = false
|
|
15
|
+
|
|
16
|
+
[Makefile]
|
|
17
|
+
indent_style = tab
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*.ipynb linguist-documentation
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# wads CI — calls the reusable workflow hosted in i2mint/wads.
|
|
2
|
+
#
|
|
3
|
+
# All configuration comes from this repo's pyproject.toml [tool.wads.ci.*].
|
|
4
|
+
# To customize the workflow itself (rare), replace this file with the
|
|
5
|
+
# full inline template `wads/data/github_ci_uv.yml` from i2mint/wads.
|
|
6
|
+
#
|
|
7
|
+
# Pinning: `@master` floats with wads. If you need version stability for
|
|
8
|
+
# a release-sensitive repo, change `@master` to a wads tag (e.g. `@v0.1.81`).
|
|
9
|
+
# CI failure does not block a published release — it blocks the publish
|
|
10
|
+
# step itself — so floating master is generally safe.
|
|
11
|
+
#
|
|
12
|
+
# Permissions: GitHub validates that the caller grants AT LEAST the
|
|
13
|
+
# permissions any job in the called workflow requests — at workflow-parse
|
|
14
|
+
# time, not at run-time, even if the job would be skipped via `if:`.
|
|
15
|
+
# The reusable workflow needs:
|
|
16
|
+
# contents: write for the publish job's version-bump push-back
|
|
17
|
+
# and for the github-pages job's gh-pages branch push
|
|
18
|
+
# pages: write for the github-pages job's REST API Pages config
|
|
19
|
+
# Both default to `write` on org-account GITHUB_TOKEN and need to be
|
|
20
|
+
# granted explicitly on personal-account callers (where the default is
|
|
21
|
+
# read-only). No `id-token: write` needed — the publish-github-pages
|
|
22
|
+
# action uses peaceiris/actions-gh-pages (branch-based) + REST API,
|
|
23
|
+
# not the OIDC `actions/deploy-pages` flow.
|
|
24
|
+
name: Continuous Integration
|
|
25
|
+
on: [push, pull_request]
|
|
26
|
+
jobs:
|
|
27
|
+
ci:
|
|
28
|
+
uses: i2mint/wads/.github/workflows/uv-ci.yml@master
|
|
29
|
+
permissions:
|
|
30
|
+
contents: write
|
|
31
|
+
pages: write
|
|
32
|
+
# Explicit pass-through (not `secrets: inherit`) because `inherit` does
|
|
33
|
+
# not reliably propagate caller-repo secrets to a reusable workflow
|
|
34
|
+
# owned by a different account (verified empirically: caller in a
|
|
35
|
+
# personal account, called in i2mint org → `${{ secrets.PYPI_PASSWORD }}`
|
|
36
|
+
# resolved to empty inside the reusable workflow despite the secret
|
|
37
|
+
# being set on the caller repo). Listing each secret here makes the
|
|
38
|
+
# propagation unambiguous regardless of caller-vs-called ownership.
|
|
39
|
+
# Missing secrets on the caller resolve to empty strings, harmless for
|
|
40
|
+
# the optional ones; PYPI_PASSWORD must be set for the publish job.
|
|
41
|
+
secrets:
|
|
42
|
+
PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
|
43
|
+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
44
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
45
|
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
|
46
|
+
HUGGINGFACE_TOKEN: ${{ secrets.HUGGINGFACE_TOKEN }}
|
|
47
|
+
KAGGLE_USERNAME: ${{ secrets.KAGGLE_USERNAME }}
|
|
48
|
+
KAGGLE_KEY: ${{ secrets.KAGGLE_KEY }}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# wads NPM CI — calls the reusable workflow hosted in i2mint/wads.
|
|
2
|
+
#
|
|
3
|
+
# All configuration lives in ts/package.json under the "wads"
|
|
4
|
+
# key (wads.ci.*). This workflow only runs when the JS subdirectory changes,
|
|
5
|
+
# so it never collides with the Python ci.yml.
|
|
6
|
+
#
|
|
7
|
+
# Publishing to npm is OPT-IN: set wads.ci.publish.enabled=true in package.json
|
|
8
|
+
# and include the marker (default "[publish-npm]") in your commit message.
|
|
9
|
+
# Publishing uses OIDC trusted publishing by default (configure the trusted
|
|
10
|
+
# publisher for this repo + workflow filename on npmjs.com). NPM_TOKEN is only
|
|
11
|
+
# needed as a fallback (e.g. the very first publish of a new package name).
|
|
12
|
+
name: NPM CI (ts)
|
|
13
|
+
on:
|
|
14
|
+
push:
|
|
15
|
+
paths:
|
|
16
|
+
- "ts/**"
|
|
17
|
+
- ".github/workflows/npm-ci-ts.yml"
|
|
18
|
+
pull_request:
|
|
19
|
+
paths:
|
|
20
|
+
- "ts/**"
|
|
21
|
+
jobs:
|
|
22
|
+
npm-ci:
|
|
23
|
+
uses: i2mint/wads/.github/workflows/npm-ci.yml@master
|
|
24
|
+
permissions:
|
|
25
|
+
contents: read
|
|
26
|
+
id-token: write # npm provenance / OIDC trusted publishing
|
|
27
|
+
with:
|
|
28
|
+
package-dir: ts
|
|
29
|
+
secrets:
|
|
30
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
.claude/handoffs/
|
|
2
|
+
.claude/scratch/
|
|
3
|
+
|
|
4
|
+
# Node / TypeScript (ts/ frontend, generated by `wads --frontend ts`)
|
|
5
|
+
node_modules/
|
|
6
|
+
*.tsbuildinfo
|
|
7
|
+
|
|
8
|
+
# Byte-compiled / optimized / DLL files
|
|
9
|
+
__pycache__/
|
|
10
|
+
*.py[cod]
|
|
11
|
+
*$py.class
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
.DS_Store
|
|
15
|
+
# C extensions
|
|
16
|
+
*.so
|
|
17
|
+
|
|
18
|
+
# TLS certificates
|
|
19
|
+
## Ignore all PEM files anywhere
|
|
20
|
+
*.pem
|
|
21
|
+
## Also ignore any certs directory
|
|
22
|
+
certs/
|
|
23
|
+
|
|
24
|
+
# Distribution / packaging
|
|
25
|
+
.Python
|
|
26
|
+
build/
|
|
27
|
+
develop-eggs/
|
|
28
|
+
dist/
|
|
29
|
+
downloads/
|
|
30
|
+
eggs/
|
|
31
|
+
.eggs/
|
|
32
|
+
lib/
|
|
33
|
+
lib64/
|
|
34
|
+
parts/
|
|
35
|
+
sdist/
|
|
36
|
+
var/
|
|
37
|
+
wheels/
|
|
38
|
+
*.egg-info/
|
|
39
|
+
.installed.cfg
|
|
40
|
+
*.egg
|
|
41
|
+
MANIFEST
|
|
42
|
+
_build
|
|
43
|
+
|
|
44
|
+
# PyInstaller
|
|
45
|
+
# Usually these files are written by a python script from a template
|
|
46
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
47
|
+
*.manifest
|
|
48
|
+
*.spec
|
|
49
|
+
|
|
50
|
+
# Installer logs
|
|
51
|
+
pip-log.txt
|
|
52
|
+
pip-delete-this-directory.txt
|
|
53
|
+
|
|
54
|
+
# Unit test / coverage reports
|
|
55
|
+
htmlcov/
|
|
56
|
+
.tox/
|
|
57
|
+
.coverage
|
|
58
|
+
.coverage.*
|
|
59
|
+
.cache
|
|
60
|
+
nosetests.xml
|
|
61
|
+
coverage.xml
|
|
62
|
+
*.cover
|
|
63
|
+
.hypothesis/
|
|
64
|
+
.pytest_cache/
|
|
65
|
+
|
|
66
|
+
# Translations
|
|
67
|
+
*.mo
|
|
68
|
+
*.pot
|
|
69
|
+
|
|
70
|
+
# Django stuff:
|
|
71
|
+
*.log
|
|
72
|
+
local_settings.py
|
|
73
|
+
db.sqlite3
|
|
74
|
+
|
|
75
|
+
# Flask stuff:
|
|
76
|
+
instance/
|
|
77
|
+
.webassets-cache
|
|
78
|
+
|
|
79
|
+
# Scrapy stuff:
|
|
80
|
+
.scrapy
|
|
81
|
+
|
|
82
|
+
# Sphinx documentation
|
|
83
|
+
docs/_build/
|
|
84
|
+
docs/*
|
|
85
|
+
|
|
86
|
+
# PyBuilder
|
|
87
|
+
target/
|
|
88
|
+
|
|
89
|
+
# Jupyter Notebook
|
|
90
|
+
.ipynb_checkpoints
|
|
91
|
+
|
|
92
|
+
# pyenv
|
|
93
|
+
.python-version
|
|
94
|
+
|
|
95
|
+
# celery beat schedule file
|
|
96
|
+
celerybeat-schedule
|
|
97
|
+
|
|
98
|
+
# SageMath parsed files
|
|
99
|
+
*.sage.py
|
|
100
|
+
|
|
101
|
+
# Environments
|
|
102
|
+
.env
|
|
103
|
+
.venv
|
|
104
|
+
env/
|
|
105
|
+
venv/
|
|
106
|
+
ENV/
|
|
107
|
+
env.bak/
|
|
108
|
+
venv.bak/
|
|
109
|
+
|
|
110
|
+
# Spyder project settings
|
|
111
|
+
.spyderproject
|
|
112
|
+
.spyproject
|
|
113
|
+
|
|
114
|
+
# Rope project settings
|
|
115
|
+
.ropeproject
|
|
116
|
+
|
|
117
|
+
# mkdocs documentation
|
|
118
|
+
/site
|
|
119
|
+
|
|
120
|
+
# mypy
|
|
121
|
+
.mypy_cache/
|
|
122
|
+
|
|
123
|
+
# PyCharm
|
|
124
|
+
.idea
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# walkthru — Decisions & Deviations
|
|
2
|
+
|
|
3
|
+
> Deliverable 2 of the build brief. Every place this project deviates from
|
|
4
|
+
> `misc/docs/WALKTHRU-AGENT-BRIEF.md` or either report, plus every genuinely open judgment
|
|
5
|
+
> call — with the reasoning and what in the code justified it. Candid, not deferential, per
|
|
6
|
+
> the brief's instruction. Granular follow-up lives in the repo's enhancement issues.
|
|
7
|
+
|
|
8
|
+
Legend: **[deviation]** departs from a stated brief/report conclusion · **[call]** an open
|
|
9
|
+
judgment the brief left to us · **[confirm]** inspection confirmed a brief assumption.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## D1. SSOT authored in Python (Pydantic), not Zod-first — **[deviation]**
|
|
14
|
+
|
|
15
|
+
**Brief said:** Zod is the presumptive SSOT; `zodal` is the presumptive schema layer; author
|
|
16
|
+
the Demo Document in TS-Zod / `zodal` and emit JSON Schema for the renderer.
|
|
17
|
+
|
|
18
|
+
**Decision:** Author the SSOT **once in Python Pydantic v2**; emit JSON Schema; **codegen Zod
|
|
19
|
+
v4** for the TS side.
|
|
20
|
+
|
|
21
|
+
**What justified it:**
|
|
22
|
+
|
|
23
|
+
- `reelee` defines its content schemas as **Pydantic** `BaseModel`s registered via
|
|
24
|
+
`lacing.register_body_schema`, exports JSON Schema (`reelee export-schemas`), and
|
|
25
|
+
**`reelee-web` codegens its Zod types from that export** (`schemas/` → `src/types/generated/`,
|
|
26
|
+
never hand-written). The federation's SSOT direction is **Python → JSON Schema → Zod**, the
|
|
27
|
+
opposite of the brief's bias.
|
|
28
|
+
- Matching this lets the Demo Document register as a `lacing` body schema and round-trip
|
|
29
|
+
losslessly with the renderer side, instead of fighting the grain of the ecosystem.
|
|
30
|
+
- `zodal` turned out to be **collection-centric** (`defineCollection`, `DataProvider`,
|
|
31
|
+
list/filter/sort) and TS-only — a poor fit for authoring a single nested document schema. It
|
|
32
|
+
is the right tool for *presenting a library of* Demo Documents/Steps as a collection, not for
|
|
33
|
+
defining the document itself.
|
|
34
|
+
|
|
35
|
+
**Cost / risk:** the *live* engine runs TS-side (capture taps acture in the browser), so TS
|
|
36
|
+
consumes codegened types rather than authoring them. This is exactly how `reelee-web` already
|
|
37
|
+
operates, so the toolchain exists. Ecosystem-independence is preserved: Pydantic is a
|
|
38
|
+
permissive third-party dep, not an ecosystem package; the `lacing` hook is an optional
|
|
39
|
+
`ecosystem/` adapter.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## D2. First `RenderTarget` is reelee's Ken Burns contract, not Remotion — **[deviation]**
|
|
44
|
+
|
|
45
|
+
**Brief/report said:** treat the hand-off as Remotion-style "input props"; Report 02 used
|
|
46
|
+
Remotion as a proxy because "Reelee could not be identified from public sources."
|
|
47
|
+
|
|
48
|
+
**Decision:** The first `RenderTarget` adapter targets **`reelee`'s actual contract** — a
|
|
49
|
+
`reelee.Project` graph projected to an ordered `list[PanelView]`, rendered by
|
|
50
|
+
`render_kenburns_video(project, out, ...)` (MoviePy/ffmpeg). Remotion becomes a *secondary,
|
|
51
|
+
external* adapter built only if a real need appears.
|
|
52
|
+
|
|
53
|
+
**What justified it:** `reelee` is a real, inspectable ecosystem package and is **not** a
|
|
54
|
+
React/props renderer. Its render input is `PanelView` records (`reelee/storyboard_export.py`) +
|
|
55
|
+
`render_kenburns_video` kwargs (`reelee/kenburns_video.py`). The brief itself instructed:
|
|
56
|
+
"inspect `reelee`'s actual input contract and write the first `RenderTarget` adapter against
|
|
57
|
+
it — the real contract supersedes the Remotion proxy; only the field-mapping adapter changes."
|
|
58
|
+
This decision follows that instruction.
|
|
59
|
+
|
|
60
|
+
**Unchanged:** the boundary principle (we own representation, the renderer owns pixels; hand off
|
|
61
|
+
validated JSON; the renderer may ignore what it doesn't understand) holds exactly as written.
|
|
62
|
+
|
|
63
|
+
**Resolved (was an open question): feed `PanelView`s, don't rebuild a `reelee.Project`.** Issue #3
|
|
64
|
+
left open whether the adapter constructs a `reelee.Project` graph or feeds panels directly.
|
|
65
|
+
`render_kenburns_video(project, …)` immediately calls `collect_panel_views(project)` — it
|
|
66
|
+
re-derives panels from an `nw`/`lacing`/falaw annotation + content-addressed-artifact graph.
|
|
67
|
+
Reconstructing that graph from a `DemoDocument` would bleed reelee's whole internal model across
|
|
68
|
+
the firewall, only for reelee to throw our panels away and rebuild its own. So
|
|
69
|
+
`walkthru.ecosystem.reelee` maps the resolved `Timeline` **directly** to `list[PanelView]` and
|
|
70
|
+
drives the film with the *same lower-level primitives* `render_kenburns_video` uses internally
|
|
71
|
+
(`burns.ken_burns_path` + an injectable `film_renderer`, default
|
|
72
|
+
`reelee.kenburns_video.default_film_renderer`). Per-panel screen time comes from the **walkthru
|
|
73
|
+
timeline** (the SSOT already composes it), not reelee's shot-timing strategies. The step-level
|
|
74
|
+
`poster` (AssetRef) added in this work is the panel image (`PanelView.image_path`).
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## D3. The command layer is `acture`; reuse its record/replay primitives — **[confirm + call]**
|
|
79
|
+
|
|
80
|
+
**Brief said:** reuse the command layer's registry + middleware; find the cleanest capture
|
|
81
|
+
interception point.
|
|
82
|
+
|
|
83
|
+
**Finding:** the layer is **`acture`** (TS/React, Apache-2.0). It already ships, in
|
|
84
|
+
`acture/packages/e2e-playwright/src/sequence.ts`:
|
|
85
|
+
|
|
86
|
+
- `replaySequence(registry, sequence, {ctx, onStep, stopOnError})` — generative replay.
|
|
87
|
+
- `recordSequence(registry) → {steps, stop()}` — capture, by wrapping `registry.dispatch`.
|
|
88
|
+
|
|
89
|
+
**Decision/call:** `walkthru`'s `play()` is a cue/narration/camera-aware **superset of
|
|
90
|
+
`replaySequence`**, and its `ActionRecorder` is a Demo-Document-emitting **superset of
|
|
91
|
+
`recordSequence`**. We do **not** invent a new engine or a new extension paradigm — observers
|
|
92
|
+
wrap `registry.dispatch` exactly as `acture-telemetry`/`acture-devtools` already do. acture has
|
|
93
|
+
**no core middleware chain by design** (dispatch interception is an opt-in adapter concern);
|
|
94
|
+
`acture-walkthru` slots in as one more opt-in dispatch-observer package beside
|
|
95
|
+
`acture-e2e-playwright` and `acture-telemetry`.
|
|
96
|
+
|
|
97
|
+
**Implication:** much of the TS engine is *composition over acture*, not new code. This is the
|
|
98
|
+
single biggest scope reduction inspection produced.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## D4. Command atom mirrors acture's `SequenceStep {commandId, params}` — **[confirm]**
|
|
103
|
+
|
|
104
|
+
The brief's `command:{id, params}` maps to acture's macro step shape exactly:
|
|
105
|
+
`SequenceStep { commandId: string, params?: unknown }` (`sequence.ts`). The full command
|
|
106
|
+
*definition* is a richer `CommandRecord {id, title, params: ZodType, when, execute, …}`, but
|
|
107
|
+
the *serializable macro step* — what a Demo Document stores — is `{commandId, params}`. We adopt
|
|
108
|
+
`{ command: { id, params } }` in the Demo Document and bridge `id ↔ commandId` in the acture
|
|
109
|
+
adapter. acture constrains `params` to the JSON-Schema-representable Zod subset; our Pydantic
|
|
110
|
+
models honor the same subset so codegen stays faithful.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## D5. License = MIT — **[call]**
|
|
115
|
+
|
|
116
|
+
**Brief said:** pick a permissive license consistent with the ecosystem.
|
|
117
|
+
|
|
118
|
+
**Decision:** **MIT.** The Python house default across `dol`, `nw`, `lacing`, `reelee`, and
|
|
119
|
+
`zodal` is MIT. `acture` is the lone **Apache-2.0** package; since `acture-walkthru` builds on
|
|
120
|
+
acture, Apache-2.0 is the relevant *inbound* license, and MIT is outbound-compatible with it
|
|
121
|
+
(MIT code may depend on Apache-2.0). MIT keeps walkthru aligned with the Python orbit it
|
|
122
|
+
publishes into while remaining compatible with the acture tie.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## D6. Layout = wads current defaults (Python `name/name/` at root); frontend CI deferred — **[call]**
|
|
127
|
+
|
|
128
|
+
**Brief proposed:** `py/src/walkthru/…`. An earlier draft of this doc used a `py/` + `ts/`
|
|
129
|
+
split. Both are superseded.
|
|
130
|
+
|
|
131
|
+
**Decision:** Use **wads current defaults**. The Python package sits at the **repo root** in the
|
|
132
|
+
`name/name/` form — `walkthru/walkthru/` — exactly what `wads populate` produces (hatchling,
|
|
133
|
+
`requires-python >= 3.10`, MIT, ruff `D100`, `tests/`, `[tool.wads.ci.*]` SSOT, the 5-line
|
|
134
|
+
`.github/workflows/ci.yml` uv stub, plus the wads `.gitignore`/`.gitattributes`/`.editorconfig`).
|
|
135
|
+
The TypeScript side lives in a sibling **`ts/`** subdir as a **single** npm package
|
|
136
|
+
(`acture-walkthru`).
|
|
137
|
+
|
|
138
|
+
**What justified it (and the constraint):** the user maintains ~200 repos in the `name/name`
|
|
139
|
+
form and wants the Python convention left untouched. wads already models the frontend as a
|
|
140
|
+
*separate, additive* overlay (`profiles.py` / `npm_config.py` + a path-filtered workflow), so
|
|
141
|
+
supporting a TS frontend alongside the unchanged Python layout costs nothing. Confirmed by
|
|
142
|
+
reading wads: `populate` (Python) and `apply_npm_overlay` (frontend) are independent code paths.
|
|
143
|
+
|
|
144
|
+
**Frontend CI deferred — coordination, not indecision.** wads' frontend overlay is brand-new and
|
|
145
|
+
unused (zodal was set up independently/earlier), so its defaults can change freely. We filed
|
|
146
|
+
**i2mint/wads#39** to generalize the single npm overlay into a **js/ts language-profile
|
|
147
|
+
registry** (extensible, with a monorepo seam), to be built in a separate session. To avoid the
|
|
148
|
+
two sessions colliding, walkthru:
|
|
149
|
+
|
|
150
|
+
- ships the **Python side on wads current defaults now** (this commit), with a **Python-only**
|
|
151
|
+
`ci.yml`; and
|
|
152
|
+
- **defers `ts/` scaffolding + `npm-ci.yml`** until the wads `ts` profile lands, then generates
|
|
153
|
+
them via `wads --frontend ts`.
|
|
154
|
+
|
|
155
|
+
**Single TS package, not a monorepo (for MVP).** `acture`/`zodal` are pnpm+turbo monorepos, but
|
|
156
|
+
walkthru's MVP needs only one publishable package. The core/adapter firewall is enforced
|
|
157
|
+
*within* one package (subpath exports + an import-boundary lint such as dependency-cruiser +
|
|
158
|
+
optional peer deps) — separate packages would be premature. The wads single-package overlay
|
|
159
|
+
fits; we graduate to the `ts-monorepo` profile (reserved in #39) only if adapters ever need
|
|
160
|
+
independent versioning.
|
|
161
|
+
|
|
162
|
+
**Pre-release CI toggles.** Because there is no release artifact, generated docs, or metrics
|
|
163
|
+
history yet, `[tool.wads.ci].publish/docs/metrics` are set to `enabled = false` (testing +
|
|
164
|
+
ruff stay on so CI gives real signal). Flip `publish` on at the first release; `docs` on once
|
|
165
|
+
docs exist. This is a phase toggle, not a convention change.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## D7. Pydantic v2 over plain dataclasses for the Python SSOT — **[call]**
|
|
170
|
+
|
|
171
|
+
The brief left the Python mirror open (dataclasses / pydantic / generated). Chose **Pydantic
|
|
172
|
+
v2**: it gives validation *and* JSON-Schema emission in one step (the whole point of the SSOT),
|
|
173
|
+
and it is what the federation already uses, so `lacing` schema registration and the reelee
|
|
174
|
+
round-trip come for free. The `core` remains pure — schema validation is side-effect-free; only
|
|
175
|
+
`play()`'s injected observers/ports perform effects.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## D8. Anchor is the single SSOT for cue/narration → step association — **[call]**
|
|
180
|
+
|
|
181
|
+
**Brief listed** `cueRefs?` and `narrationRef?` on `CommandStep`, *and* anchors on the cue and
|
|
182
|
+
narration tracks — i.e. the association stored in two places.
|
|
183
|
+
|
|
184
|
+
**Decision:** Drop the step-side `cueRefs`/`narrationRef`. The **track item's `anchor`**
|
|
185
|
+
(`{stepId, localOffsetMs[, durationMs]}`) is the *only* place a cue/narration is tied to a step.
|
|
186
|
+
The engine resolves "what fires at this step" by filtering the tracks on `anchor.stepId`
|
|
187
|
+
(`_cues_for` / `_narration_for`).
|
|
188
|
+
|
|
189
|
+
**Why:** storing the same association on both the step and the track item is a dual-source-of-truth
|
|
190
|
+
that drifts on every edit (the user's SSOT principle, and Report 02's "wrong abstraction"
|
|
191
|
+
warning). The anchor is also strictly richer — it carries `localOffset` (and `duration` for
|
|
192
|
+
narration) that a bare id reference cannot — so it must exist regardless; the step-side ref is pure
|
|
193
|
+
redundancy. Finding a step's cues is an O(n) track scan, which is irrelevant at demo sizes. If a
|
|
194
|
+
real need for fast reverse lookup ever appears, it is a derived index, not new SSOT.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## D9. Lifecycle protocol realized as a typed event stream, not named hooks — **[deviation]**
|
|
199
|
+
|
|
200
|
+
**Brief/Report 02 §B.1** describe the lifecycle as an `Observer` object with named methods
|
|
201
|
+
(`onStepEnter`, `beforeCommand`, `onCueBegin`, …).
|
|
202
|
+
|
|
203
|
+
**Decision:** Realize it as a stream of **typed event dataclasses** (`StepEnter`, `BeforeCommand`,
|
|
204
|
+
`CueBegin`, …) consumed by observer **callables** (`Observer = Callable[[Event], Awaitable|None]`).
|
|
205
|
+
Each event type maps 1:1 to a brief hook name, so nothing is lost; the canonical walk is the async
|
|
206
|
+
generator `iter_events(document, executor)`, and `play()` is a thin driver that fans events to
|
|
207
|
+
observers.
|
|
208
|
+
|
|
209
|
+
**Why:** it matches Thor's functional-over-OOP convention and the iterables guidance (a lazy
|
|
210
|
+
`yield`-based stream rather than an object with 13 optional override points). Composition is
|
|
211
|
+
trivial (an observer is just a function; filtering by `isinstance` selects the events you care
|
|
212
|
+
about), and the same event vocabulary serves both `play` (generative) and `record` (capture) —
|
|
213
|
+
the "one lifecycle protocol, driver inverted" the brief wants. A named-hook adapter can be layered
|
|
214
|
+
on later for anyone who prefers it (progressive disclosure), but it is not the core mechanism.
|
|
215
|
+
|
|
216
|
+
**Async core — [call].** The engine and ports are `async` because the real executors (acture
|
|
217
|
+
dispatch, Playwright) and recorders do I/O. The pure-core tests need no async plugin — they use
|
|
218
|
+
`asyncio.run` over fakes. `executor` and observers may be sync *or* async; the engine awaits
|
|
219
|
+
whatever is awaitable (`_maybe_await`).
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## D10. Zod codegen is a self-contained `ts/` build script, not reelee-web's toolchain — **[call]**
|
|
224
|
+
|
|
225
|
+
**Open question (was PLAN §9):** where the Zod codegen lives — a `schema/` build script vs.
|
|
226
|
+
reusing reelee-web's codegen toolchain.
|
|
227
|
+
|
|
228
|
+
**Decision:** a self-contained `ts/scripts/codegen.mjs` that consumes the committed
|
|
229
|
+
`schema/demo-document.schema.json` (the Pydantic-emitted artifact) and writes
|
|
230
|
+
`ts/src/schema.generated.ts`. It uses the **same tools** reelee-web uses — `json-schema-to-zod` +
|
|
231
|
+
zod v4 — but **not** reelee's `export-schemas` CLI or `lacing` body-schema registry.
|
|
232
|
+
|
|
233
|
+
**Why:** reelee-web's `scripts/codegen.mjs` is coupled to `python -m reelee export-schemas` and a
|
|
234
|
+
`lacing` `index.json` registry — machinery walkthru does not have, and whose adoption PLAN §9 still
|
|
235
|
+
defers (the `lacing` body-schema question). Reusing the *technique* without the *coupling* keeps
|
|
236
|
+
the TS side runnable with no Python toolchain and preserves the core/adapter firewall: TS depends
|
|
237
|
+
only on the JSON contract, never on `walkthru`/`reelee`/`lacing`.
|
|
238
|
+
|
|
239
|
+
**Two gotchas handled:** (1) `json-schema-to-zod` does **not** dereference `$ref` (CLI *or* API) —
|
|
240
|
+
it renders Pydantic's `$defs` refs as `z.any()`; we inline them first with
|
|
241
|
+
`@apidevtools/json-schema-ref-parser` (the Demo Document schema is a DAG, so full dereference is
|
|
242
|
+
safe). (2) Drift is guarded both sides: Python pins JSON Schema ↔ Pydantic
|
|
243
|
+
(`test_committed_json_schema_is_up_to_date`); TS pins the committed Zod ↔ JSON Schema via
|
|
244
|
+
`codegen.mjs --check` (run by `ts/src/schema.codegen.test.ts`). Discriminated unions arrive as
|
|
245
|
+
`oneOf` → json-schema-to-zod's "exactly one variant passes" `superRefine`, which is faithful
|
|
246
|
+
(closed union; unknown `type`/`kind` rejected — covered by the round-trip negatives).
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Open judgment calls deferred to issues (not yet decided)
|
|
251
|
+
|
|
252
|
+
- Whether `play()` needs a **Python mirror** for MVP or whether TS-only suffices until render.
|
|
253
|
+
- Whether to register the Demo Document as a **`lacing` body schema** in MVP or defer.
|
|
254
|
+
|
|
255
|
+
These are tracked as enhancement issues — the project's permanent development memory.
|
walkthru-0.0.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thor Whalen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
walkthru-0.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: walkthru
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Turn a sequence of app commands into an editable, re-renderable demo/tour artifact.
|
|
5
|
+
Project-URL: Homepage, https://github.com/thorwhalen/walkthru
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: annotation,automation,demo,screencast,tour,walkthrough
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: pydantic>=2
|
|
11
|
+
Provides-Extra: reelee
|
|
12
|
+
Requires-Dist: burns; extra == 'reelee'
|
|
13
|
+
Requires-Dist: mixing; extra == 'reelee'
|
|
14
|
+
Requires-Dist: reelee; extra == 'reelee'
|
|
15
|
+
Provides-Extra: testing
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# walkthru
|
|
19
|
+
|
|
20
|
+
Turn a sequence of application **commands** into an **editable, re-renderable demo/tour
|
|
21
|
+
artifact** — and play that sequence while observers record video, draw visual cues, and
|
|
22
|
+
narrate.
|
|
23
|
+
|
|
24
|
+
`walkthru` owns the **representation** (the *Demo Document*) and the **playback/capture
|
|
25
|
+
engine**. It does **not** render the final video — it hands a validated artifact off to a
|
|
26
|
+
renderer (the `reelee` ecosystem, Remotion, moviepy/ffmpeg, …). *Owning representation, not
|
|
27
|
+
pixels, is the load-bearing boundary of the whole design.*
|
|
28
|
+
|
|
29
|
+
## Two modes, one data model, one engine
|
|
30
|
+
|
|
31
|
+
- **Generative** — an author supplies a Demo Document (commands + annotations); `walkthru`
|
|
32
|
+
plays it while recording, with optional cues (highlight, spotlight, hotspot, callout,
|
|
33
|
+
synthetic cursor), pauses, and narration.
|
|
34
|
+
- **Capture** — a human operates the app manually; `walkthru` records the video **and** the
|
|
35
|
+
underlying command stream **and** annotations, producing the *same* Demo Document.
|
|
36
|
+
|
|
37
|
+
The only difference between the modes is *who fills in the document*.
|
|
38
|
+
|
|
39
|
+
## The core
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
play(demoDoc, executor, observers) -> Outcome
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
A pure higher-order function that walks the document and emits lifecycle events
|
|
46
|
+
(`onStepEnter` → `beforeCommand` → `afterCommand` → `onStepExit`, `onCueBegin/End`,
|
|
47
|
+
`onNarration`, …). It never records, renders, or speaks — every effect is an injected
|
|
48
|
+
observer or port.
|
|
49
|
+
|
|
50
|
+
## Ecosystem-biased, ecosystem-independent
|
|
51
|
+
|
|
52
|
+
The core and ports depend on **nothing** from our ecosystem. Integration with
|
|
53
|
+
[`acture`](https://github.com/thorwhalen/acture) (the command layer),
|
|
54
|
+
[`reelee`](https://github.com/thorwhalen/reelee) (the renderer), and `zodal` ships as
|
|
55
|
+
**optional adapters**. You can `pip install walkthru` / `npm i acture-walkthru` and use the
|
|
56
|
+
core with your own adapters.
|
|
57
|
+
|
|
58
|
+
## Packages
|
|
59
|
+
|
|
60
|
+
| Package | Registry | Role |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `walkthru` | PyPI | Python core + schema SSOT + render hand-off |
|
|
63
|
+
| `acture-walkthru` | npm | TS core + the live capture/play engine over `acture` |
|
|
64
|
+
|
|
65
|
+
## Status
|
|
66
|
+
|
|
67
|
+
🚧 **Planning.** See [`PLAN.md`](./PLAN.md) for the implementation plan,
|
|
68
|
+
[`DECISIONS.md`](./DECISIONS.md) for design decisions and deviations from the brief, and the
|
|
69
|
+
repo's **enhancement issues** for the running development journal. Source documents live in
|
|
70
|
+
[`misc/docs/`](./misc/docs/).
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT — see [`LICENSE`](./LICENSE).
|