test(svglide): close theme system p0 plan evidence
@@ -0,0 +1,308 @@
|
||||
# SVGlide Theme System P0 Evidence
|
||||
|
||||
This file is the reviewer-facing evidence for the Theme System P0 plan in
|
||||
`/Users/bytedance/Desktop/SVGlide-Theme-System-立项文档.md`.
|
||||
|
||||
## Scope
|
||||
|
||||
P0 proves a minimal executable loop:
|
||||
|
||||
- ThemeSpec exists and rejects invalid themes.
|
||||
- `theme_validate` validates plan/canvas `theme_id` against the registry.
|
||||
- `theme_adherence` checks final `04-svg/prepared/*.svg` colors and direct
|
||||
text contrast.
|
||||
- `quality_gate` consumes theme receipts and freshness.
|
||||
- `production_live` requires a current human `pre_submit_review` before
|
||||
`live_create`.
|
||||
- `direct_svg` is not blocked by artboard package checks.
|
||||
|
||||
P0 does not claim automatic aesthetics, image/gradient sampling, full CSS
|
||||
cascade, editor UI, slide server changes, PPTX export, animation, narrated
|
||||
output, or a complete theme/template product surface.
|
||||
|
||||
P1/P2 are not targets for this evidence slice. In particular, this file does
|
||||
not claim model-driven theme extraction, productized theme authoring UI,
|
||||
cross-deck theme migration, export packaging, or automated aesthetic approval.
|
||||
|
||||
## Branch And Baseline
|
||||
|
||||
Observed during this evidence update:
|
||||
|
||||
```text
|
||||
worktree: /Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private
|
||||
branch: feat/svglide-artboard-satori
|
||||
implementation base HEAD before evidence patch: 842e2d44dec7a16f1b432687e2ae4e49fb9b2333
|
||||
```
|
||||
|
||||
The original document's section 0 described an older target HEAD where many
|
||||
files were still dirty overlay. Current branch HEAD already contains the Theme
|
||||
System implementation files from commit `842e2d44`.
|
||||
|
||||
## P0 Checklist
|
||||
|
||||
| Item | Status | Evidence |
|
||||
| --- | --- | --- |
|
||||
| P0-0 baseline normalization | PASS | artboard renderer, package check, canvas schema, and template registry are tracked in HEAD |
|
||||
| P0-1 ThemeSpec schema | PASS | `references/svglide-theme-spec.schema.json`; `svglide_theme_test.py` |
|
||||
| P0-2 theme utilities | PASS | `svglide_theme.py` covers registry/theme load, stable hashes, static SVG colors, contrast |
|
||||
| P0-3 theme_validate | PASS | `svglide_theme_validate.py` writes `06-check/theme-validate.json` and `receipts/theme-validate.json` |
|
||||
| P0-4 theme_adherence | PASS | `svglide_theme_adherence.py` checks final prepared SVG colors, contrast, and stale theme validation |
|
||||
| P0-5 pre_submit_review | PASS | `svglide_pre_submit_review.py` requires structured human approval and freshness |
|
||||
| P0-6a runner theme/package stages | PASS | `svglide_project_runner.py` supports `theme_validate`, `package_check`, `theme_adherence` |
|
||||
| P0-6b production_live submit gate | PASS | `production_live` path requires current `pre_submit_review` before `live_create` |
|
||||
| P0-7 quality_gate integration | PASS | `svglide_quality_gate.py` consumes theme receipts; `artboard_satori` requires package check; `direct_svg` does not |
|
||||
| P0-8 fixtures/evidence/regression | PASS | positive and negative fixtures added under `fixtures/svglide_artboard/theme-system-*`; this file records commands |
|
||||
|
||||
Contract details closed during this update:
|
||||
|
||||
- `pre_submit_review` accepts both the keyed object format and the document
|
||||
example format `[{kind, path, sha256}]` for `reviewed_artifacts`.
|
||||
- runner `pre_submit_review` uses the documented default human input file:
|
||||
`06-check/pre-submit-human-review.json`.
|
||||
|
||||
## Fixture Coverage
|
||||
|
||||
Positive fixtures:
|
||||
|
||||
- `skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-p0/artboard-satori`
|
||||
- three pages: cover, content, closing
|
||||
- `generation_mode=artboard_satori`
|
||||
- expected: `theme_validate=passed`, `theme_adherence=passed`
|
||||
- `skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-p0/direct-svg`
|
||||
- one direct SVG page
|
||||
- expected: `theme_validate=passed`, `theme_adherence=passed`
|
||||
- used to document that `direct_svg` does not require artboard package check
|
||||
|
||||
Negative fixtures:
|
||||
|
||||
- `skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/unknown-color`
|
||||
- expected issue: `theme_unknown_color`
|
||||
- `skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/low-contrast`
|
||||
- expected issue: `contrast_too_low`
|
||||
- `skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/stale-theme-validate`
|
||||
- expected issue: `theme_validate_plan_stale`
|
||||
|
||||
The fixture regression is executable through:
|
||||
|
||||
```bash
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 -m unittest skills/lark-slides/scripts/svglide_theme_system_p0_fixture_test.py
|
||||
```
|
||||
|
||||
## P0-8 Receipt Paths
|
||||
|
||||
Positive receipt paths written by the fixture commands:
|
||||
|
||||
```text
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-p0/artboard-satori/06-check/theme-validate.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-p0/artboard-satori/receipts/theme-validate.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-p0/artboard-satori/06-check/theme-adherence.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-p0/artboard-satori/receipts/theme-adherence.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-p0/direct-svg/06-check/theme-validate.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-p0/direct-svg/receipts/theme-validate.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-p0/direct-svg/06-check/theme-adherence.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-p0/direct-svg/receipts/theme-adherence.json
|
||||
```
|
||||
|
||||
Negative receipt paths written by the fixture commands:
|
||||
|
||||
```text
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/unknown-color/06-check/theme-validate.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/unknown-color/receipts/theme-validate.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/unknown-color/06-check/theme-adherence.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/unknown-color/receipts/theme-adherence.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/low-contrast/06-check/theme-validate.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/low-contrast/receipts/theme-validate.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/low-contrast/06-check/theme-adherence.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/low-contrast/receipts/theme-adherence.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/stale-theme-validate/06-check/theme-validate.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/stale-theme-validate/receipts/theme-validate.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/stale-theme-validate/06-check/theme-adherence.json
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/stale-theme-validate/receipts/theme-adherence.json
|
||||
```
|
||||
|
||||
## Commands Run
|
||||
|
||||
Command templates used for reviewer reproduction:
|
||||
|
||||
```bash
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 skills/lark-slides/scripts/svglide_theme_validate.py <project_root> --pretty
|
||||
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 skills/lark-slides/scripts/svglide_theme_adherence.py <project_root> --pretty
|
||||
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 -m unittest <test_file_or_test_case>
|
||||
```
|
||||
|
||||
Real fixture commands run for this P0-8 evidence pass:
|
||||
|
||||
```bash
|
||||
git -C /Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private status --short --branch
|
||||
git -C /Users/bytedance/bd-projects/workspaces/SVGlide/.worktrees/cli-svglide-svg-private rev-parse HEAD
|
||||
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 skills/lark-slides/scripts/svglide_theme_validate.py \
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-p0/artboard-satori \
|
||||
--pretty
|
||||
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 skills/lark-slides/scripts/svglide_theme_validate.py \
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-p0/direct-svg \
|
||||
--pretty
|
||||
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 skills/lark-slides/scripts/svglide_theme_validate.py \
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/unknown-color \
|
||||
--pretty
|
||||
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 skills/lark-slides/scripts/svglide_theme_validate.py \
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/low-contrast \
|
||||
--pretty
|
||||
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 skills/lark-slides/scripts/svglide_theme_adherence.py \
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-p0/artboard-satori \
|
||||
--pretty
|
||||
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 skills/lark-slides/scripts/svglide_theme_adherence.py \
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-p0/direct-svg \
|
||||
--pretty
|
||||
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 skills/lark-slides/scripts/svglide_theme_adherence.py \
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/unknown-color \
|
||||
--pretty
|
||||
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 skills/lark-slides/scripts/svglide_theme_adherence.py \
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/low-contrast \
|
||||
--pretty
|
||||
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 skills/lark-slides/scripts/svglide_theme_adherence.py \
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-invalid/stale-theme-validate \
|
||||
--pretty
|
||||
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 -m unittest \
|
||||
skills/lark-slides/scripts/svglide_theme_validate_test.py \
|
||||
skills/lark-slides/scripts/svglide_theme_adherence_test.py
|
||||
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 -m unittest skills/lark-slides/scripts/svglide_theme_system_p0_fixture_test.py
|
||||
```
|
||||
|
||||
Observed P0-8 command results:
|
||||
|
||||
```text
|
||||
branch: feat/svglide-artboard-satori
|
||||
HEAD: 842e2d44dec7a16f1b432687e2ae4e49fb9b2333
|
||||
artboard-satori theme_validate: passed, page_count=3, theme_count=1
|
||||
artboard-satori theme_adherence: passed, prepared_svg_count=3
|
||||
direct-svg theme_validate: passed, page_count=1, theme_count=1
|
||||
direct-svg theme_adherence: passed, prepared_svg_count=1
|
||||
unknown-color theme_validate: passed
|
||||
unknown-color theme_adherence: failed as expected with theme_unknown_color
|
||||
low-contrast theme_validate: passed
|
||||
low-contrast theme_adherence: failed as expected with contrast_too_low
|
||||
stale-theme-validate theme_adherence: failed as expected with theme_validate_plan_stale
|
||||
svglide_theme_validate_test.py + svglide_theme_adherence_test.py: Ran 11 tests, OK
|
||||
theme_system_p0_fixture_test.py: Ran 4 tests, OK
|
||||
```
|
||||
|
||||
Targeted P0 tests:
|
||||
|
||||
```bash
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 -m unittest \
|
||||
skills/lark-slides/scripts/svglide_theme_test.py \
|
||||
skills/lark-slides/scripts/svglide_theme_validate_test.py \
|
||||
skills/lark-slides/scripts/svglide_theme_adherence_test.py \
|
||||
skills/lark-slides/scripts/svglide_pre_submit_review_test.py \
|
||||
skills/lark-slides/scripts/svglide_project_runner_test.py \
|
||||
skills/lark-slides/scripts/svglide_quality_gate_test.py \
|
||||
skills/lark-slides/scripts/svglide_theme_system_p0_fixture_test.py
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```text
|
||||
Ran 103 tests in 8.395s
|
||||
OK
|
||||
```
|
||||
|
||||
Full scripts unittest discovery:
|
||||
|
||||
```bash
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 -m unittest discover skills/lark-slides/scripts -p '*_test.py'
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```text
|
||||
Ran 335 tests in 16.636s
|
||||
OK
|
||||
```
|
||||
|
||||
Package check tests:
|
||||
|
||||
```bash
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 -m unittest skills/lark-slides/scripts/svglide_artboard_package_check_test.py
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```text
|
||||
Ran 4 tests in 0.127s
|
||||
OK
|
||||
```
|
||||
|
||||
Go root package test:
|
||||
|
||||
```bash
|
||||
env GOCACHE=/private/tmp/svglide-gocache go test .
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```text
|
||||
ok github.com/larksuite/cli 0.739s
|
||||
```
|
||||
|
||||
Package runtime check:
|
||||
|
||||
```bash
|
||||
env PYTHONPYCACHEPREFIX=/private/tmp/svglide-pycache \
|
||||
python3 skills/lark-slides/scripts/svglide_artboard_package_check.py \
|
||||
--output-dir /private/tmp/svglide-theme-system-p0-package-check-final \
|
||||
--pretty
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```text
|
||||
status: passed
|
||||
runtime_check_count: 2
|
||||
output receipts:
|
||||
/private/tmp/svglide-theme-system-p0-package-check-final/06-check/artboard-package-check.json
|
||||
/private/tmp/svglide-theme-system-p0-package-check-final/receipts/artboard-package-check.json
|
||||
```
|
||||
|
||||
## Key Runtime Boundaries
|
||||
|
||||
- `pre_submit_review` is a human receipt validator. It is not an automatic
|
||||
aesthetics model.
|
||||
- `theme_adherence` only checks static SVG colors and direct text/background
|
||||
contrast in final prepared SVG files.
|
||||
- Satori SVG remains an artboard preview/intermediate artifact; final theme
|
||||
adherence is checked on `04-svg/prepared/*.svg`.
|
||||
- `direct_svg` still runs theme checks, but it must not require
|
||||
`artboard-package-check`.
|
||||
- `export` is outside Theme System P0. The P0 plan explicitly does not claim
|
||||
PPTX or narrated deck output.
|
||||
@@ -0,0 +1,9 @@
|
||||
Theme System P0 negative fixtures.
|
||||
|
||||
Each child directory is a project root for a specific failing condition:
|
||||
|
||||
- `unknown-color`: prepared SVG contains a color outside the active ThemeSpec.
|
||||
- `low-contrast`: prepared SVG uses allowed colors but fails text contrast.
|
||||
- `stale-theme-validate`: `theme-validate.json` is intentionally stale.
|
||||
|
||||
These fixtures are exercised by `svglide_theme_system_p0_fixture_test.py`.
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"generation_mode": "artboard_satori",
|
||||
"language": "zh-CN",
|
||||
"theme_id": "dark-clarity",
|
||||
"slides": [
|
||||
{
|
||||
"page": 1,
|
||||
"title": "低对比度负例",
|
||||
"canvas_spec": {
|
||||
"version": "svglide-canvas-spec/v1",
|
||||
"template_id": "cover-hero",
|
||||
"theme_id": "dark-clarity"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540"><rect width="960" height="540" fill="#0F172A"/><text x="88" y="140" fill="#111827">低对比度负例</text></svg>
|
||||
|
After Width: | Height: | Size: 202 B |
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"action": "repair_and_rerun",
|
||||
"checked_at": "2026-06-21T21:34:08+08:00",
|
||||
"contrast_failures": [
|
||||
{
|
||||
"min": 4.5,
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"ratio": 1.0064,
|
||||
"text_index": 0
|
||||
}
|
||||
],
|
||||
"contrast_unresolved": [],
|
||||
"inputs": {
|
||||
"plan_sha256": "9d54b389938c4a1d52a2ff9ef4cffacf7fc90d4282aee5154ba85d1b78198fc3",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"theme_validate": "06-check/theme-validate.json",
|
||||
"theme_validate_sha256": "0d72cbf9f836201ee3e7f8589a2b2fbdd00907e5e89a69640d5060bc98d7c65b"
|
||||
},
|
||||
"issues": [
|
||||
{
|
||||
"code": "contrast_too_low",
|
||||
"message": "text contrast 1.01 is below 4.5",
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg"
|
||||
}
|
||||
],
|
||||
"output_path": "06-check/theme-adherence.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [
|
||||
{
|
||||
"code": "contrast_too_low",
|
||||
"message": "text contrast 1.01 is below 4.5",
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg"
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "b18e154042df52110f187d53683dbb3faff57944db65a5c218e313b57615d7f6",
|
||||
"status": "failed",
|
||||
"theme_id": "dark-clarity"
|
||||
}
|
||||
],
|
||||
"prepared_files": [
|
||||
{
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "b18e154042df52110f187d53683dbb3faff57944db65a5c218e313b57615d7f6"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-adherence/v1",
|
||||
"stage": "theme_adherence",
|
||||
"status": "failed",
|
||||
"summary": {
|
||||
"contrast_unresolved_count": 0,
|
||||
"error_count": 1,
|
||||
"prepared_svg_count": 1,
|
||||
"unknown_color_count": 0,
|
||||
"warning_count": 0
|
||||
},
|
||||
"unknown_colors": []
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"action": "create_live",
|
||||
"checked_at": "2026-06-21T21:34:00+08:00",
|
||||
"inputs": {
|
||||
"plan_sha256": "9d54b389938c4a1d52a2ff9ef4cffacf7fc90d4282aee5154ba85d1b78198fc3",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"template_registry": "skills/lark-slides/references/svglide-template-registry.json",
|
||||
"template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3",
|
||||
"theme_registry": "skills/lark-slides/scripts/artboard_renderer/themes/registry.json",
|
||||
"theme_registry_sha256": "62ea0e69b9f5380a11622a6a47f6947943601f4cc3f1bdba1c7f4e45d77db959"
|
||||
},
|
||||
"issues": [],
|
||||
"output_path": "06-check/theme-validate.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [],
|
||||
"page": 1,
|
||||
"status": "passed",
|
||||
"template_id": "cover-hero",
|
||||
"theme_id": "dark-clarity",
|
||||
"theme_sha256": "707e671d244fb10d975a10da1c78692b215e8544bc44eb1bb4c0a55dfaec01c1"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-validate/v1",
|
||||
"stage": "theme_validate",
|
||||
"status": "passed",
|
||||
"summary": {
|
||||
"error_count": 0,
|
||||
"page_count": 1,
|
||||
"theme_count": 1,
|
||||
"warning_count": 0
|
||||
},
|
||||
"theme_files": [
|
||||
{
|
||||
"path": "skills/lark-slides/scripts/artboard_renderer/themes/dark-clarity.json",
|
||||
"sha256": "f47cfdcc5c29d4b694820c097a8cccedc964d072211b579c0a91e3702189ab1b"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"action": "repair_and_rerun",
|
||||
"checked_at": "2026-06-21T21:34:08+08:00",
|
||||
"contrast_failures": [
|
||||
{
|
||||
"min": 4.5,
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"ratio": 1.0064,
|
||||
"text_index": 0
|
||||
}
|
||||
],
|
||||
"contrast_unresolved": [],
|
||||
"inputs": {
|
||||
"plan_sha256": "9d54b389938c4a1d52a2ff9ef4cffacf7fc90d4282aee5154ba85d1b78198fc3",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"theme_validate": "06-check/theme-validate.json",
|
||||
"theme_validate_sha256": "0d72cbf9f836201ee3e7f8589a2b2fbdd00907e5e89a69640d5060bc98d7c65b"
|
||||
},
|
||||
"issues": [
|
||||
{
|
||||
"code": "contrast_too_low",
|
||||
"message": "text contrast 1.01 is below 4.5",
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg"
|
||||
}
|
||||
],
|
||||
"output_path": "06-check/theme-adherence.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [
|
||||
{
|
||||
"code": "contrast_too_low",
|
||||
"message": "text contrast 1.01 is below 4.5",
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg"
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "b18e154042df52110f187d53683dbb3faff57944db65a5c218e313b57615d7f6",
|
||||
"status": "failed",
|
||||
"theme_id": "dark-clarity"
|
||||
}
|
||||
],
|
||||
"prepared_files": [
|
||||
{
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "b18e154042df52110f187d53683dbb3faff57944db65a5c218e313b57615d7f6"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-adherence/v1",
|
||||
"stage": "theme_adherence",
|
||||
"status": "failed",
|
||||
"summary": {
|
||||
"contrast_unresolved_count": 0,
|
||||
"error_count": 1,
|
||||
"prepared_svg_count": 1,
|
||||
"unknown_color_count": 0,
|
||||
"warning_count": 0
|
||||
},
|
||||
"unknown_colors": []
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"action": "create_live",
|
||||
"checked_at": "2026-06-21T21:34:00+08:00",
|
||||
"inputs": {
|
||||
"plan_sha256": "9d54b389938c4a1d52a2ff9ef4cffacf7fc90d4282aee5154ba85d1b78198fc3",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"template_registry": "skills/lark-slides/references/svglide-template-registry.json",
|
||||
"template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3",
|
||||
"theme_registry": "skills/lark-slides/scripts/artboard_renderer/themes/registry.json",
|
||||
"theme_registry_sha256": "62ea0e69b9f5380a11622a6a47f6947943601f4cc3f1bdba1c7f4e45d77db959"
|
||||
},
|
||||
"issues": [],
|
||||
"output_path": "06-check/theme-validate.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [],
|
||||
"page": 1,
|
||||
"status": "passed",
|
||||
"template_id": "cover-hero",
|
||||
"theme_id": "dark-clarity",
|
||||
"theme_sha256": "707e671d244fb10d975a10da1c78692b215e8544bc44eb1bb4c0a55dfaec01c1"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-validate/v1",
|
||||
"stage": "theme_validate",
|
||||
"status": "passed",
|
||||
"summary": {
|
||||
"error_count": 0,
|
||||
"page_count": 1,
|
||||
"theme_count": 1,
|
||||
"warning_count": 0
|
||||
},
|
||||
"theme_files": [
|
||||
{
|
||||
"path": "skills/lark-slides/scripts/artboard_renderer/themes/dark-clarity.json",
|
||||
"sha256": "f47cfdcc5c29d4b694820c097a8cccedc964d072211b579c0a91e3702189ab1b"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"generation_mode": "artboard_satori",
|
||||
"language": "zh-CN",
|
||||
"theme_id": "dark-clarity",
|
||||
"slides": [
|
||||
{
|
||||
"page": 1,
|
||||
"title": "stale receipt 负例",
|
||||
"canvas_spec": {
|
||||
"version": "svglide-canvas-spec/v1",
|
||||
"template_id": "cover-hero",
|
||||
"theme_id": "dark-clarity"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540"><rect width="960" height="540" fill="#0F172A"/><text x="88" y="140" fill="#F8FAFC">stale receipt 负例</text></svg>
|
||||
|
After Width: | Height: | Size: 204 B |
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"action": "repair_and_rerun",
|
||||
"checked_at": "2026-06-21T21:34:11+08:00",
|
||||
"contrast_failures": [],
|
||||
"contrast_unresolved": [],
|
||||
"inputs": {
|
||||
"plan_sha256": "776295d1edea25ac19696a5b5114ada5cce7a99b2a02ecc0a8a3ac65d697f121",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"theme_validate": "06-check/theme-validate.json",
|
||||
"theme_validate_sha256": "bc51b146bf85d911dda444f8b43b955347fdeee6c69b9bb467d5fceff4b1ebe3"
|
||||
},
|
||||
"issues": [
|
||||
{
|
||||
"code": "theme_validate_plan_stale",
|
||||
"message": "theme_validate plan_sha256 does not match current slide_plan.json",
|
||||
"path": "06-check/theme-validate.json"
|
||||
}
|
||||
],
|
||||
"output_path": "06-check/theme-adherence.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [],
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "6287b6edeed2bf3219eaa1035a5a922406f47d7d905b2e34ab6f1899901aab92",
|
||||
"status": "passed",
|
||||
"theme_id": "dark-clarity"
|
||||
}
|
||||
],
|
||||
"prepared_files": [
|
||||
{
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "6287b6edeed2bf3219eaa1035a5a922406f47d7d905b2e34ab6f1899901aab92"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-adherence/v1",
|
||||
"stage": "theme_adherence",
|
||||
"status": "failed",
|
||||
"summary": {
|
||||
"contrast_unresolved_count": 0,
|
||||
"error_count": 1,
|
||||
"prepared_svg_count": 1,
|
||||
"unknown_color_count": 0,
|
||||
"warning_count": 0
|
||||
},
|
||||
"unknown_colors": []
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"action": "create_live",
|
||||
"checked_at": "2026-06-21T00:00:00+08:00",
|
||||
"inputs": {
|
||||
"plan_sha256": "stale",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"template_registry": "skills/lark-slides/references/svglide-template-registry.json",
|
||||
"template_registry_sha256": "stale",
|
||||
"theme_registry": "skills/lark-slides/scripts/artboard_renderer/themes/registry.json",
|
||||
"theme_registry_sha256": "stale"
|
||||
},
|
||||
"issues": [],
|
||||
"output_path": "06-check/theme-validate.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [],
|
||||
"page": 1,
|
||||
"status": "passed",
|
||||
"template_id": "cover-hero",
|
||||
"theme_id": "dark-clarity",
|
||||
"theme_sha256": "stale"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-validate/v1",
|
||||
"stage": "theme_validate",
|
||||
"status": "passed",
|
||||
"summary": {
|
||||
"error_count": 0,
|
||||
"page_count": 1,
|
||||
"theme_count": 1,
|
||||
"warning_count": 0
|
||||
},
|
||||
"theme_files": []
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"action": "repair_and_rerun",
|
||||
"checked_at": "2026-06-21T21:34:11+08:00",
|
||||
"contrast_failures": [],
|
||||
"contrast_unresolved": [],
|
||||
"inputs": {
|
||||
"plan_sha256": "776295d1edea25ac19696a5b5114ada5cce7a99b2a02ecc0a8a3ac65d697f121",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"theme_validate": "06-check/theme-validate.json",
|
||||
"theme_validate_sha256": "bc51b146bf85d911dda444f8b43b955347fdeee6c69b9bb467d5fceff4b1ebe3"
|
||||
},
|
||||
"issues": [
|
||||
{
|
||||
"code": "theme_validate_plan_stale",
|
||||
"message": "theme_validate plan_sha256 does not match current slide_plan.json",
|
||||
"path": "06-check/theme-validate.json"
|
||||
}
|
||||
],
|
||||
"output_path": "06-check/theme-adherence.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [],
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "6287b6edeed2bf3219eaa1035a5a922406f47d7d905b2e34ab6f1899901aab92",
|
||||
"status": "passed",
|
||||
"theme_id": "dark-clarity"
|
||||
}
|
||||
],
|
||||
"prepared_files": [
|
||||
{
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "6287b6edeed2bf3219eaa1035a5a922406f47d7d905b2e34ab6f1899901aab92"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-adherence/v1",
|
||||
"stage": "theme_adherence",
|
||||
"status": "failed",
|
||||
"summary": {
|
||||
"contrast_unresolved_count": 0,
|
||||
"error_count": 1,
|
||||
"prepared_svg_count": 1,
|
||||
"unknown_color_count": 0,
|
||||
"warning_count": 0
|
||||
},
|
||||
"unknown_colors": []
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"action": "create_live",
|
||||
"checked_at": "2026-06-21T00:00:00+08:00",
|
||||
"inputs": {
|
||||
"plan_sha256": "stale",
|
||||
"slide_plan": "02-plan/slide_plan.json"
|
||||
},
|
||||
"issues": [],
|
||||
"output_path": "06-check/theme-validate.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [],
|
||||
"page": 1,
|
||||
"status": "passed",
|
||||
"template_id": "cover-hero",
|
||||
"theme_id": "dark-clarity",
|
||||
"theme_sha256": "stale"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-validate/v1",
|
||||
"stage": "theme_validate",
|
||||
"status": "passed",
|
||||
"summary": {
|
||||
"error_count": 0,
|
||||
"page_count": 1,
|
||||
"theme_count": 1,
|
||||
"warning_count": 0
|
||||
},
|
||||
"theme_files": []
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"generation_mode": "artboard_satori",
|
||||
"language": "zh-CN",
|
||||
"theme_id": "dark-clarity",
|
||||
"slides": [
|
||||
{
|
||||
"page": 1,
|
||||
"title": "未知颜色负例",
|
||||
"canvas_spec": {
|
||||
"version": "svglide-canvas-spec/v1",
|
||||
"template_id": "cover-hero",
|
||||
"theme_id": "dark-clarity"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540"><rect width="960" height="540" fill="#0F172A"/><text x="88" y="140" fill="#F8FAFC">未知颜色负例</text><circle cx="760" cy="160" r="72" fill="#123456"/></svg>
|
||||
|
After Width: | Height: | Size: 251 B |
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"action": "repair_and_rerun",
|
||||
"checked_at": "2026-06-21T21:34:08+08:00",
|
||||
"contrast_failures": [],
|
||||
"contrast_unresolved": [],
|
||||
"inputs": {
|
||||
"plan_sha256": "37ec82d0f615e7daf9ab498684597ebc93748372a1f8c56daec548ba01a3e425",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"theme_validate": "06-check/theme-validate.json",
|
||||
"theme_validate_sha256": "bec8dd0adf58083b70607bfa7d1ddaabd67b53826bf1c15e43c92348bb999d12"
|
||||
},
|
||||
"issues": [
|
||||
{
|
||||
"code": "theme_unknown_color",
|
||||
"message": "color #123456 is not allowed by theme dark-clarity",
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg"
|
||||
}
|
||||
],
|
||||
"output_path": "06-check/theme-adherence.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [
|
||||
{
|
||||
"code": "theme_unknown_color",
|
||||
"message": "color #123456 is not allowed by theme dark-clarity",
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg"
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "9a86436dad4a18fcb65e7a41a37ed7c6aa6f59bd79ef15f041feaa16675e62b7",
|
||||
"status": "failed",
|
||||
"theme_id": "dark-clarity"
|
||||
}
|
||||
],
|
||||
"prepared_files": [
|
||||
{
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "9a86436dad4a18fcb65e7a41a37ed7c6aa6f59bd79ef15f041feaa16675e62b7"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-adherence/v1",
|
||||
"stage": "theme_adherence",
|
||||
"status": "failed",
|
||||
"summary": {
|
||||
"contrast_unresolved_count": 0,
|
||||
"error_count": 1,
|
||||
"prepared_svg_count": 1,
|
||||
"unknown_color_count": 1,
|
||||
"warning_count": 0
|
||||
},
|
||||
"unknown_colors": [
|
||||
{
|
||||
"color": "#123456",
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"action": "create_live",
|
||||
"checked_at": "2026-06-21T21:34:00+08:00",
|
||||
"inputs": {
|
||||
"plan_sha256": "37ec82d0f615e7daf9ab498684597ebc93748372a1f8c56daec548ba01a3e425",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"template_registry": "skills/lark-slides/references/svglide-template-registry.json",
|
||||
"template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3",
|
||||
"theme_registry": "skills/lark-slides/scripts/artboard_renderer/themes/registry.json",
|
||||
"theme_registry_sha256": "62ea0e69b9f5380a11622a6a47f6947943601f4cc3f1bdba1c7f4e45d77db959"
|
||||
},
|
||||
"issues": [],
|
||||
"output_path": "06-check/theme-validate.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [],
|
||||
"page": 1,
|
||||
"status": "passed",
|
||||
"template_id": "cover-hero",
|
||||
"theme_id": "dark-clarity",
|
||||
"theme_sha256": "707e671d244fb10d975a10da1c78692b215e8544bc44eb1bb4c0a55dfaec01c1"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-validate/v1",
|
||||
"stage": "theme_validate",
|
||||
"status": "passed",
|
||||
"summary": {
|
||||
"error_count": 0,
|
||||
"page_count": 1,
|
||||
"theme_count": 1,
|
||||
"warning_count": 0
|
||||
},
|
||||
"theme_files": [
|
||||
{
|
||||
"path": "skills/lark-slides/scripts/artboard_renderer/themes/dark-clarity.json",
|
||||
"sha256": "f47cfdcc5c29d4b694820c097a8cccedc964d072211b579c0a91e3702189ab1b"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"action": "repair_and_rerun",
|
||||
"checked_at": "2026-06-21T21:34:08+08:00",
|
||||
"contrast_failures": [],
|
||||
"contrast_unresolved": [],
|
||||
"inputs": {
|
||||
"plan_sha256": "37ec82d0f615e7daf9ab498684597ebc93748372a1f8c56daec548ba01a3e425",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"theme_validate": "06-check/theme-validate.json",
|
||||
"theme_validate_sha256": "bec8dd0adf58083b70607bfa7d1ddaabd67b53826bf1c15e43c92348bb999d12"
|
||||
},
|
||||
"issues": [
|
||||
{
|
||||
"code": "theme_unknown_color",
|
||||
"message": "color #123456 is not allowed by theme dark-clarity",
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg"
|
||||
}
|
||||
],
|
||||
"output_path": "06-check/theme-adherence.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [
|
||||
{
|
||||
"code": "theme_unknown_color",
|
||||
"message": "color #123456 is not allowed by theme dark-clarity",
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg"
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "9a86436dad4a18fcb65e7a41a37ed7c6aa6f59bd79ef15f041feaa16675e62b7",
|
||||
"status": "failed",
|
||||
"theme_id": "dark-clarity"
|
||||
}
|
||||
],
|
||||
"prepared_files": [
|
||||
{
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "9a86436dad4a18fcb65e7a41a37ed7c6aa6f59bd79ef15f041feaa16675e62b7"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-adherence/v1",
|
||||
"stage": "theme_adherence",
|
||||
"status": "failed",
|
||||
"summary": {
|
||||
"contrast_unresolved_count": 0,
|
||||
"error_count": 1,
|
||||
"prepared_svg_count": 1,
|
||||
"unknown_color_count": 1,
|
||||
"warning_count": 0
|
||||
},
|
||||
"unknown_colors": [
|
||||
{
|
||||
"color": "#123456",
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"action": "create_live",
|
||||
"checked_at": "2026-06-21T21:34:00+08:00",
|
||||
"inputs": {
|
||||
"plan_sha256": "37ec82d0f615e7daf9ab498684597ebc93748372a1f8c56daec548ba01a3e425",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"template_registry": "skills/lark-slides/references/svglide-template-registry.json",
|
||||
"template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3",
|
||||
"theme_registry": "skills/lark-slides/scripts/artboard_renderer/themes/registry.json",
|
||||
"theme_registry_sha256": "62ea0e69b9f5380a11622a6a47f6947943601f4cc3f1bdba1c7f4e45d77db959"
|
||||
},
|
||||
"issues": [],
|
||||
"output_path": "06-check/theme-validate.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [],
|
||||
"page": 1,
|
||||
"status": "passed",
|
||||
"template_id": "cover-hero",
|
||||
"theme_id": "dark-clarity",
|
||||
"theme_sha256": "707e671d244fb10d975a10da1c78692b215e8544bc44eb1bb4c0a55dfaec01c1"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-validate/v1",
|
||||
"stage": "theme_validate",
|
||||
"status": "passed",
|
||||
"summary": {
|
||||
"error_count": 0,
|
||||
"page_count": 1,
|
||||
"theme_count": 1,
|
||||
"warning_count": 0
|
||||
},
|
||||
"theme_files": [
|
||||
{
|
||||
"path": "skills/lark-slides/scripts/artboard_renderer/themes/dark-clarity.json",
|
||||
"sha256": "f47cfdcc5c29d4b694820c097a8cccedc964d072211b579c0a91e3702189ab1b"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
Theme System P0 positive fixtures.
|
||||
|
||||
Use these project roots to prove that ThemeSpec validation and final SVG
|
||||
theme adherence can pass without relying on plan-only visual distinctness.
|
||||
|
||||
- `artboard-satori`: three-page cover/content/closing artboard project.
|
||||
- `direct-svg`: one-page direct SVG project; it validates theme usage without
|
||||
requiring the artboard package check.
|
||||
|
||||
Recommended smoke commands:
|
||||
|
||||
```bash
|
||||
python3 skills/lark-slides/scripts/svglide_theme_validate.py \
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-p0/artboard-satori \
|
||||
--pretty
|
||||
python3 skills/lark-slides/scripts/svglide_theme_adherence.py \
|
||||
skills/lark-slides/scripts/fixtures/svglide_artboard/theme-system-p0/artboard-satori \
|
||||
--pretty
|
||||
```
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"generation_mode": "artboard_satori",
|
||||
"language": "zh-CN",
|
||||
"theme_id": "dark-clarity",
|
||||
"slides": [
|
||||
{
|
||||
"page": 1,
|
||||
"page_type": "cover",
|
||||
"title": "主题系统封面",
|
||||
"canvas_spec": {
|
||||
"version": "svglide-canvas-spec/v1",
|
||||
"template_id": "cover-hero",
|
||||
"theme_id": "dark-clarity",
|
||||
"theme": {
|
||||
"colors": {
|
||||
"background": "#0F172A",
|
||||
"text": "#F8FAFC"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"page": 2,
|
||||
"page_type": "content",
|
||||
"title": "主题系统正文",
|
||||
"canvas_spec": {
|
||||
"version": "svglide-canvas-spec/v1",
|
||||
"template_id": "comparison-cards",
|
||||
"theme_id": "dark-clarity",
|
||||
"theme": {
|
||||
"colors": {
|
||||
"background": "#0F172A",
|
||||
"text": "#F8FAFC"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"page": 3,
|
||||
"page_type": "closing",
|
||||
"title": "主题系统结尾",
|
||||
"canvas_spec": {
|
||||
"version": "svglide-canvas-spec/v1",
|
||||
"template_id": "summary-final",
|
||||
"theme_id": "dark-clarity",
|
||||
"theme": {
|
||||
"colors": {
|
||||
"background": "#0F172A",
|
||||
"text": "#F8FAFC"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540"><rect width="960" height="540" fill="#0F172A"/><rect x="72" y="72" width="640" height="300" fill="#111827"/><text x="96" y="150" fill="#F8FAFC">主题系统封面</text><text x="96" y="210" fill="#CBD5E1">final prepared SVG 使用 ThemeSpec 颜色</text><circle cx="800" cy="130" r="64" fill="#38BDF8"/></svg>
|
||||
|
After Width: | Height: | Size: 397 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540"><rect width="960" height="540" fill="#0F172A"/><rect x="80" y="96" width="360" height="280" fill="#111827"/><rect x="520" y="96" width="360" height="280" fill="#111827"/><text x="112" y="152" fill="#F8FAFC">主题系统正文</text><text x="112" y="210" fill="#CBD5E1">左侧观点</text><text x="552" y="210" fill="#CBD5E1">右侧观点</text><path d="M112 300 L250 240 L388 300" stroke="#38BDF8" fill="none"/></svg>
|
||||
|
After Width: | Height: | Size: 505 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540"><rect width="960" height="540" fill="#0F172A"/><rect x="96" y="112" width="768" height="260" fill="#111827"/><text x="132" y="180" fill="#F8FAFC">主题系统结尾</text><text x="132" y="242" fill="#CBD5E1">ThemeSpec、adherence 与 quality gate 闭环</text><rect x="132" y="300" width="168" height="40" fill="#A78BFA"/></svg>
|
||||
|
After Width: | Height: | Size: 416 B |
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"action": "create_live",
|
||||
"checked_at": "2026-06-21T21:34:08+08:00",
|
||||
"contrast_failures": [],
|
||||
"contrast_unresolved": [],
|
||||
"inputs": {
|
||||
"plan_sha256": "4831ac4c6c82d461a671a67561e50fe6111af4c6baba957a2640512c39517afd",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"theme_validate": "06-check/theme-validate.json",
|
||||
"theme_validate_sha256": "3a16056b5947ff22a2145592ef6a774c92c7b292272913527a837ec998ab295d"
|
||||
},
|
||||
"issues": [],
|
||||
"output_path": "06-check/theme-adherence.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [],
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "f6f43d6b72f7872fcdc80f7a6130d16a2247f9b0ec2bdbd40fb6e0d2e9a336ef",
|
||||
"status": "passed",
|
||||
"theme_id": "dark-clarity"
|
||||
},
|
||||
{
|
||||
"issues": [],
|
||||
"page": 2,
|
||||
"path": "04-svg/prepared/page-002.svg",
|
||||
"sha256": "fe7e6b41731a60b3cc48a9431640104f55374cf4f4a69548072546e1b51f7301",
|
||||
"status": "passed",
|
||||
"theme_id": "dark-clarity"
|
||||
},
|
||||
{
|
||||
"issues": [],
|
||||
"page": 3,
|
||||
"path": "04-svg/prepared/page-003.svg",
|
||||
"sha256": "a4a09d108e7bfb33ca8104e4261983794e48a8a4b947fcdf5f06c820e6936bf0",
|
||||
"status": "passed",
|
||||
"theme_id": "dark-clarity"
|
||||
}
|
||||
],
|
||||
"prepared_files": [
|
||||
{
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "f6f43d6b72f7872fcdc80f7a6130d16a2247f9b0ec2bdbd40fb6e0d2e9a336ef"
|
||||
},
|
||||
{
|
||||
"path": "04-svg/prepared/page-002.svg",
|
||||
"sha256": "fe7e6b41731a60b3cc48a9431640104f55374cf4f4a69548072546e1b51f7301"
|
||||
},
|
||||
{
|
||||
"path": "04-svg/prepared/page-003.svg",
|
||||
"sha256": "a4a09d108e7bfb33ca8104e4261983794e48a8a4b947fcdf5f06c820e6936bf0"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-adherence/v1",
|
||||
"stage": "theme_adherence",
|
||||
"status": "passed",
|
||||
"summary": {
|
||||
"contrast_unresolved_count": 0,
|
||||
"error_count": 0,
|
||||
"prepared_svg_count": 3,
|
||||
"unknown_color_count": 0,
|
||||
"warning_count": 0
|
||||
},
|
||||
"unknown_colors": []
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"action": "create_live",
|
||||
"checked_at": "2026-06-21T21:34:00+08:00",
|
||||
"inputs": {
|
||||
"plan_sha256": "4831ac4c6c82d461a671a67561e50fe6111af4c6baba957a2640512c39517afd",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"template_registry": "skills/lark-slides/references/svglide-template-registry.json",
|
||||
"template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3",
|
||||
"theme_registry": "skills/lark-slides/scripts/artboard_renderer/themes/registry.json",
|
||||
"theme_registry_sha256": "62ea0e69b9f5380a11622a6a47f6947943601f4cc3f1bdba1c7f4e45d77db959"
|
||||
},
|
||||
"issues": [],
|
||||
"output_path": "06-check/theme-validate.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [],
|
||||
"page": 1,
|
||||
"status": "passed",
|
||||
"template_id": "cover-hero",
|
||||
"theme_id": "dark-clarity",
|
||||
"theme_sha256": "707e671d244fb10d975a10da1c78692b215e8544bc44eb1bb4c0a55dfaec01c1"
|
||||
},
|
||||
{
|
||||
"issues": [],
|
||||
"page": 2,
|
||||
"status": "passed",
|
||||
"template_id": "comparison-cards",
|
||||
"theme_id": "dark-clarity",
|
||||
"theme_sha256": "707e671d244fb10d975a10da1c78692b215e8544bc44eb1bb4c0a55dfaec01c1"
|
||||
},
|
||||
{
|
||||
"issues": [],
|
||||
"page": 3,
|
||||
"status": "passed",
|
||||
"template_id": "summary-final",
|
||||
"theme_id": "dark-clarity",
|
||||
"theme_sha256": "707e671d244fb10d975a10da1c78692b215e8544bc44eb1bb4c0a55dfaec01c1"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-validate/v1",
|
||||
"stage": "theme_validate",
|
||||
"status": "passed",
|
||||
"summary": {
|
||||
"error_count": 0,
|
||||
"page_count": 3,
|
||||
"theme_count": 1,
|
||||
"warning_count": 0
|
||||
},
|
||||
"theme_files": [
|
||||
{
|
||||
"path": "skills/lark-slides/scripts/artboard_renderer/themes/dark-clarity.json",
|
||||
"sha256": "f47cfdcc5c29d4b694820c097a8cccedc964d072211b579c0a91e3702189ab1b"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"action": "create_live",
|
||||
"checked_at": "2026-06-21T21:34:08+08:00",
|
||||
"contrast_failures": [],
|
||||
"contrast_unresolved": [],
|
||||
"inputs": {
|
||||
"plan_sha256": "4831ac4c6c82d461a671a67561e50fe6111af4c6baba957a2640512c39517afd",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"theme_validate": "06-check/theme-validate.json",
|
||||
"theme_validate_sha256": "3a16056b5947ff22a2145592ef6a774c92c7b292272913527a837ec998ab295d"
|
||||
},
|
||||
"issues": [],
|
||||
"output_path": "06-check/theme-adherence.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [],
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "f6f43d6b72f7872fcdc80f7a6130d16a2247f9b0ec2bdbd40fb6e0d2e9a336ef",
|
||||
"status": "passed",
|
||||
"theme_id": "dark-clarity"
|
||||
},
|
||||
{
|
||||
"issues": [],
|
||||
"page": 2,
|
||||
"path": "04-svg/prepared/page-002.svg",
|
||||
"sha256": "fe7e6b41731a60b3cc48a9431640104f55374cf4f4a69548072546e1b51f7301",
|
||||
"status": "passed",
|
||||
"theme_id": "dark-clarity"
|
||||
},
|
||||
{
|
||||
"issues": [],
|
||||
"page": 3,
|
||||
"path": "04-svg/prepared/page-003.svg",
|
||||
"sha256": "a4a09d108e7bfb33ca8104e4261983794e48a8a4b947fcdf5f06c820e6936bf0",
|
||||
"status": "passed",
|
||||
"theme_id": "dark-clarity"
|
||||
}
|
||||
],
|
||||
"prepared_files": [
|
||||
{
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "f6f43d6b72f7872fcdc80f7a6130d16a2247f9b0ec2bdbd40fb6e0d2e9a336ef"
|
||||
},
|
||||
{
|
||||
"path": "04-svg/prepared/page-002.svg",
|
||||
"sha256": "fe7e6b41731a60b3cc48a9431640104f55374cf4f4a69548072546e1b51f7301"
|
||||
},
|
||||
{
|
||||
"path": "04-svg/prepared/page-003.svg",
|
||||
"sha256": "a4a09d108e7bfb33ca8104e4261983794e48a8a4b947fcdf5f06c820e6936bf0"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-adherence/v1",
|
||||
"stage": "theme_adherence",
|
||||
"status": "passed",
|
||||
"summary": {
|
||||
"contrast_unresolved_count": 0,
|
||||
"error_count": 0,
|
||||
"prepared_svg_count": 3,
|
||||
"unknown_color_count": 0,
|
||||
"warning_count": 0
|
||||
},
|
||||
"unknown_colors": []
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"action": "create_live",
|
||||
"checked_at": "2026-06-21T21:34:00+08:00",
|
||||
"inputs": {
|
||||
"plan_sha256": "4831ac4c6c82d461a671a67561e50fe6111af4c6baba957a2640512c39517afd",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"template_registry": "skills/lark-slides/references/svglide-template-registry.json",
|
||||
"template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3",
|
||||
"theme_registry": "skills/lark-slides/scripts/artboard_renderer/themes/registry.json",
|
||||
"theme_registry_sha256": "62ea0e69b9f5380a11622a6a47f6947943601f4cc3f1bdba1c7f4e45d77db959"
|
||||
},
|
||||
"issues": [],
|
||||
"output_path": "06-check/theme-validate.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [],
|
||||
"page": 1,
|
||||
"status": "passed",
|
||||
"template_id": "cover-hero",
|
||||
"theme_id": "dark-clarity",
|
||||
"theme_sha256": "707e671d244fb10d975a10da1c78692b215e8544bc44eb1bb4c0a55dfaec01c1"
|
||||
},
|
||||
{
|
||||
"issues": [],
|
||||
"page": 2,
|
||||
"status": "passed",
|
||||
"template_id": "comparison-cards",
|
||||
"theme_id": "dark-clarity",
|
||||
"theme_sha256": "707e671d244fb10d975a10da1c78692b215e8544bc44eb1bb4c0a55dfaec01c1"
|
||||
},
|
||||
{
|
||||
"issues": [],
|
||||
"page": 3,
|
||||
"status": "passed",
|
||||
"template_id": "summary-final",
|
||||
"theme_id": "dark-clarity",
|
||||
"theme_sha256": "707e671d244fb10d975a10da1c78692b215e8544bc44eb1bb4c0a55dfaec01c1"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-validate/v1",
|
||||
"stage": "theme_validate",
|
||||
"status": "passed",
|
||||
"summary": {
|
||||
"error_count": 0,
|
||||
"page_count": 3,
|
||||
"theme_count": 1,
|
||||
"warning_count": 0
|
||||
},
|
||||
"theme_files": [
|
||||
{
|
||||
"path": "skills/lark-slides/scripts/artboard_renderer/themes/dark-clarity.json",
|
||||
"sha256": "f47cfdcc5c29d4b694820c097a8cccedc964d072211b579c0a91e3702189ab1b"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"generation_mode": "direct_svg",
|
||||
"language": "zh-CN",
|
||||
"theme_id": "dark-clarity",
|
||||
"slides": [
|
||||
{
|
||||
"page": 1,
|
||||
"page_type": "content",
|
||||
"title": "Direct SVG 主题检查"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="540" viewBox="0 0 960 540"><rect width="960" height="540" fill="#0F172A"/><text x="88" y="140" fill="#F8FAFC">Direct SVG 主题检查</text><text x="88" y="196" fill="#CBD5E1">direct_svg 不依赖 artboard package check</text><rect x="88" y="252" width="220" height="48" fill="#38BDF8"/></svg>
|
||||
|
After Width: | Height: | Size: 354 B |
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"action": "create_live",
|
||||
"checked_at": "2026-06-21T21:34:08+08:00",
|
||||
"contrast_failures": [],
|
||||
"contrast_unresolved": [],
|
||||
"inputs": {
|
||||
"plan_sha256": "b17941fe2a32a4fb4b5dce9404baac08d14183ffe2ae2a9b2ff07d6b3bb0c10a",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"theme_validate": "06-check/theme-validate.json",
|
||||
"theme_validate_sha256": "dc0b34dfe8f2e2d82f2c3b9053e559b67225736e6a7806fef603b58086242eb9"
|
||||
},
|
||||
"issues": [],
|
||||
"output_path": "06-check/theme-adherence.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [],
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "e1447907a30468942c2605b6de2a0571dd6503102e3f49fe9a53958bbabd11c8",
|
||||
"status": "passed",
|
||||
"theme_id": "dark-clarity"
|
||||
}
|
||||
],
|
||||
"prepared_files": [
|
||||
{
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "e1447907a30468942c2605b6de2a0571dd6503102e3f49fe9a53958bbabd11c8"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-adherence/v1",
|
||||
"stage": "theme_adherence",
|
||||
"status": "passed",
|
||||
"summary": {
|
||||
"contrast_unresolved_count": 0,
|
||||
"error_count": 0,
|
||||
"prepared_svg_count": 1,
|
||||
"unknown_color_count": 0,
|
||||
"warning_count": 0
|
||||
},
|
||||
"unknown_colors": []
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"action": "create_live",
|
||||
"checked_at": "2026-06-21T21:34:00+08:00",
|
||||
"inputs": {
|
||||
"plan_sha256": "b17941fe2a32a4fb4b5dce9404baac08d14183ffe2ae2a9b2ff07d6b3bb0c10a",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"template_registry": "skills/lark-slides/references/svglide-template-registry.json",
|
||||
"template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3",
|
||||
"theme_registry": "skills/lark-slides/scripts/artboard_renderer/themes/registry.json",
|
||||
"theme_registry_sha256": "62ea0e69b9f5380a11622a6a47f6947943601f4cc3f1bdba1c7f4e45d77db959"
|
||||
},
|
||||
"issues": [],
|
||||
"output_path": "06-check/theme-validate.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [],
|
||||
"page": 1,
|
||||
"status": "passed",
|
||||
"template_id": null,
|
||||
"theme_id": "dark-clarity",
|
||||
"theme_sha256": "707e671d244fb10d975a10da1c78692b215e8544bc44eb1bb4c0a55dfaec01c1"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-validate/v1",
|
||||
"stage": "theme_validate",
|
||||
"status": "passed",
|
||||
"summary": {
|
||||
"error_count": 0,
|
||||
"page_count": 1,
|
||||
"theme_count": 1,
|
||||
"warning_count": 0
|
||||
},
|
||||
"theme_files": [
|
||||
{
|
||||
"path": "skills/lark-slides/scripts/artboard_renderer/themes/dark-clarity.json",
|
||||
"sha256": "f47cfdcc5c29d4b694820c097a8cccedc964d072211b579c0a91e3702189ab1b"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"action": "create_live",
|
||||
"checked_at": "2026-06-21T21:34:08+08:00",
|
||||
"contrast_failures": [],
|
||||
"contrast_unresolved": [],
|
||||
"inputs": {
|
||||
"plan_sha256": "b17941fe2a32a4fb4b5dce9404baac08d14183ffe2ae2a9b2ff07d6b3bb0c10a",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"theme_validate": "06-check/theme-validate.json",
|
||||
"theme_validate_sha256": "dc0b34dfe8f2e2d82f2c3b9053e559b67225736e6a7806fef603b58086242eb9"
|
||||
},
|
||||
"issues": [],
|
||||
"output_path": "06-check/theme-adherence.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [],
|
||||
"page": 1,
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "e1447907a30468942c2605b6de2a0571dd6503102e3f49fe9a53958bbabd11c8",
|
||||
"status": "passed",
|
||||
"theme_id": "dark-clarity"
|
||||
}
|
||||
],
|
||||
"prepared_files": [
|
||||
{
|
||||
"path": "04-svg/prepared/page-001.svg",
|
||||
"sha256": "e1447907a30468942c2605b6de2a0571dd6503102e3f49fe9a53958bbabd11c8"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-adherence/v1",
|
||||
"stage": "theme_adherence",
|
||||
"status": "passed",
|
||||
"summary": {
|
||||
"contrast_unresolved_count": 0,
|
||||
"error_count": 0,
|
||||
"prepared_svg_count": 1,
|
||||
"unknown_color_count": 0,
|
||||
"warning_count": 0
|
||||
},
|
||||
"unknown_colors": []
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"action": "create_live",
|
||||
"checked_at": "2026-06-21T21:34:00+08:00",
|
||||
"inputs": {
|
||||
"plan_sha256": "b17941fe2a32a4fb4b5dce9404baac08d14183ffe2ae2a9b2ff07d6b3bb0c10a",
|
||||
"slide_plan": "02-plan/slide_plan.json",
|
||||
"template_registry": "skills/lark-slides/references/svglide-template-registry.json",
|
||||
"template_registry_sha256": "78c6639ee435ff8b8818f64f7b536967a7486281eacab5abce8e20626e20f6b3",
|
||||
"theme_registry": "skills/lark-slides/scripts/artboard_renderer/themes/registry.json",
|
||||
"theme_registry_sha256": "62ea0e69b9f5380a11622a6a47f6947943601f4cc3f1bdba1c7f4e45d77db959"
|
||||
},
|
||||
"issues": [],
|
||||
"output_path": "06-check/theme-validate.json",
|
||||
"pages": [
|
||||
{
|
||||
"issues": [],
|
||||
"page": 1,
|
||||
"status": "passed",
|
||||
"template_id": null,
|
||||
"theme_id": "dark-clarity",
|
||||
"theme_sha256": "707e671d244fb10d975a10da1c78692b215e8544bc44eb1bb4c0a55dfaec01c1"
|
||||
}
|
||||
],
|
||||
"schema_version": "svglide-theme-validate/v1",
|
||||
"stage": "theme_validate",
|
||||
"status": "passed",
|
||||
"summary": {
|
||||
"error_count": 0,
|
||||
"page_count": 1,
|
||||
"theme_count": 1,
|
||||
"warning_count": 0
|
||||
},
|
||||
"theme_files": [
|
||||
{
|
||||
"path": "skills/lark-slides/scripts/artboard_renderer/themes/dark-clarity.json",
|
||||
"sha256": "f47cfdcc5c29d4b694820c097a8cccedc964d072211b579c0a91e3702189ab1b"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -151,6 +151,33 @@ def sorted_hash_records(records: list[dict[str, str]]) -> list[dict[str, str]]:
|
||||
return sorted(records, key=lambda item: item["path"])
|
||||
|
||||
|
||||
def normalize_reviewed_artifacts(value: Any) -> dict[str, Any] | None:
|
||||
if isinstance(value, dict):
|
||||
return value
|
||||
if not isinstance(value, list):
|
||||
return None
|
||||
normalized: dict[str, Any] = {}
|
||||
prepared: list[dict[str, str]] = []
|
||||
for item in value:
|
||||
if not isinstance(item, dict):
|
||||
return None
|
||||
kind = item.get("kind")
|
||||
path = item.get("path")
|
||||
sha = item.get("sha256")
|
||||
if not isinstance(kind, str) or not isinstance(path, str) or not isinstance(sha, str):
|
||||
return None
|
||||
record = {"path": path, "sha256": sha}
|
||||
if kind == "prepared_svg":
|
||||
prepared.append(record)
|
||||
elif kind in ARTIFACT_PATHS:
|
||||
normalized[kind] = record
|
||||
else:
|
||||
normalized[kind] = record
|
||||
if prepared:
|
||||
normalized["prepared_svg"] = prepared
|
||||
return normalized
|
||||
|
||||
|
||||
def validate_hash_value(
|
||||
issues: list[dict[str, str]],
|
||||
*,
|
||||
@@ -386,7 +413,7 @@ def validate_reviewed_artifacts(
|
||||
project: Path,
|
||||
current_prepared: list[dict[str, str]],
|
||||
) -> list[dict[str, Any]]:
|
||||
reviewed = human.get("reviewed_artifacts") if isinstance(human.get("reviewed_artifacts"), dict) else {}
|
||||
reviewed = normalize_reviewed_artifacts(human.get("reviewed_artifacts"))
|
||||
evidence: list[dict[str, Any]] = []
|
||||
if not reviewed:
|
||||
issues.append(issue("human_review", "reviewed_artifacts_missing", "human review must include reviewed_artifacts", category="missing_input"))
|
||||
@@ -446,7 +473,7 @@ def validate_human_bindings(
|
||||
elif recorded_prepared != sorted_hash_records(current_prepared):
|
||||
issues.append(issue("human_review", "human_prepared_files_stale", "human review prepared_files do not match current prepared SVG files", category="stale_hash"))
|
||||
|
||||
reviewed_artifacts = human.get("reviewed_artifacts") if isinstance(human.get("reviewed_artifacts"), dict) else {}
|
||||
reviewed_artifacts = normalize_reviewed_artifacts(human.get("reviewed_artifacts")) or {}
|
||||
quality_gate_review = reviewed_artifacts.get("quality_gate") if isinstance(reviewed_artifacts, dict) else None
|
||||
if isinstance(quality_gate_review, dict) and quality_gate_review.get("sha256") != human.get("quality_gate_sha256"):
|
||||
issues.append(issue("human_review", "human_quality_gate_hash_disagrees", "quality_gate_sha256 must match reviewed_artifacts.quality_gate.sha256", category="stale_hash"))
|
||||
|
||||
@@ -162,6 +162,25 @@ def human_review_payload(project: Path) -> dict[str, object]:
|
||||
}
|
||||
|
||||
|
||||
def human_review_payload_with_artifact_array(project: Path) -> dict[str, object]:
|
||||
payload = human_review_payload(project)
|
||||
reviewed = payload["reviewed_artifacts"]
|
||||
assert isinstance(reviewed, dict)
|
||||
current_prepared = reviewed["prepared_svg"]
|
||||
assert isinstance(current_prepared, list)
|
||||
artifacts: list[dict[str, str]] = [
|
||||
{"kind": "prepared_svg", "path": item["path"], "sha256": item["sha256"]}
|
||||
for item in current_prepared
|
||||
if isinstance(item, dict) and isinstance(item.get("path"), str) and isinstance(item.get("sha256"), str)
|
||||
]
|
||||
for kind in ("contact_sheet", "preview", "preview_manifest", "quality_gate"):
|
||||
item = reviewed[kind]
|
||||
assert isinstance(item, dict)
|
||||
artifacts.append({"kind": kind, "path": item["path"], "sha256": item["sha256"]})
|
||||
payload["reviewed_artifacts"] = artifacts
|
||||
return payload
|
||||
|
||||
|
||||
def write_human_review(project: Path, payload: dict[str, object] | None = None) -> Path:
|
||||
path = project / "06-check/pre-submit-human-review.json"
|
||||
write_json(path, payload or human_review_payload(project))
|
||||
@@ -187,6 +206,16 @@ class PreSubmitReviewTest(unittest.TestCase):
|
||||
self.assertEqual(check["status"], "passed")
|
||||
self.assertEqual(check, receipt)
|
||||
|
||||
def test_document_array_reviewed_artifacts_contract_passes(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
project = make_project(Path(tmp))
|
||||
write_human_review(project, human_review_payload_with_artifact_array(project))
|
||||
|
||||
result = pre_submit_review.run_pre_submit_review(project, project / "06-check/pre-submit-human-review.json")
|
||||
|
||||
self.assertEqual(result["status"], "passed", result["issues"])
|
||||
self.assertEqual(result["issues"], [])
|
||||
|
||||
def test_missing_human_file_writes_failed_receipt(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
project = make_project(Path(tmp))
|
||||
|
||||
@@ -1808,12 +1808,12 @@ def run_implemented_stage(project_root: Path, stage: str, state: dict[str, Any],
|
||||
(SCRIPT_DIR / "svglide_pre_submit_review.py").as_posix(),
|
||||
project_root.as_posix(),
|
||||
"--human-review",
|
||||
(project_root / "06-check" / "pre-submit-review.input.json").as_posix(),
|
||||
(project_root / "06-check" / "pre-submit-human-review.json").as_posix(),
|
||||
"--pretty",
|
||||
],
|
||||
output_json=project_root / "06-check" / "pre-submit-review.json",
|
||||
inputs=[
|
||||
"06-check/pre-submit-review.input.json",
|
||||
"06-check/pre-submit-human-review.json",
|
||||
"06-check/quality-gate.json",
|
||||
"06-check/theme-adherence.json",
|
||||
"06-check/visual-distinctness.json",
|
||||
|
||||
@@ -528,6 +528,53 @@ class SVGlideProjectRunnerTest(unittest.TestCase):
|
||||
self.assertLess(called_stages.index("pre_submit_review"), called_stages.index("live_create"))
|
||||
self.assertTrue(all(profile == "production_live" for _, profile in calls))
|
||||
|
||||
def test_pre_submit_review_uses_documented_human_review_path(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
plan_root = Path(tmpdir) / ".lark-slides/plan"
|
||||
result = runner.init_project("smoke", "Smoke", plan_root=plan_root)
|
||||
project_root = Path(result["project_root"])
|
||||
state = runner.load_state(project_root)
|
||||
state.setdefault("stages", {})["ppe_proof"] = {"status": "passed"}
|
||||
captured: dict[str, object] = {}
|
||||
original_run_script_stage = runner.run_script_stage
|
||||
|
||||
def fake_run_script_stage(
|
||||
project_root: Path,
|
||||
state: dict[str, object],
|
||||
stage: str,
|
||||
command: list[str],
|
||||
*,
|
||||
output_json: Path | None = None,
|
||||
inputs: list[str] | None = None,
|
||||
outputs: list[str] | None = None,
|
||||
command_runner=runner.subprocess.run,
|
||||
) -> dict[str, object]:
|
||||
captured["command"] = command
|
||||
captured["inputs"] = inputs
|
||||
return runner.complete_stage(
|
||||
project_root,
|
||||
state,
|
||||
stage,
|
||||
"passed",
|
||||
started_at=runner.now_iso(),
|
||||
inputs=inputs,
|
||||
outputs=outputs,
|
||||
command=command,
|
||||
)
|
||||
|
||||
try:
|
||||
runner.run_script_stage = fake_run_script_stage
|
||||
runner.run_implemented_stage(project_root, "pre_submit_review", state, profile="production_live")
|
||||
finally:
|
||||
runner.run_script_stage = original_run_script_stage
|
||||
|
||||
command = captured["command"]
|
||||
inputs = captured["inputs"]
|
||||
self.assertIsInstance(command, list)
|
||||
self.assertIsInstance(inputs, list)
|
||||
self.assertIn((project_root / "06-check/pre-submit-human-review.json").as_posix(), command)
|
||||
self.assertIn("06-check/pre-submit-human-review.json", inputs)
|
||||
|
||||
def test_init_creates_project_directories_manifest_and_state(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
plan_root = Path(tmpdir) / ".lark-slides/plan"
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
# SPDX-License-Identifier: MIT
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
|
||||
import svglide_theme_adherence
|
||||
import svglide_theme_validate
|
||||
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
FIXTURE_ROOT = SCRIPT_DIR / "fixtures" / "svglide_artboard"
|
||||
EVIDENCE_PATH = SCRIPT_DIR.parent / "references" / "svglide-theme-system-p0-evidence.md"
|
||||
|
||||
|
||||
def copy_fixture(relative: str, tmpdir: str) -> Path:
|
||||
source = FIXTURE_ROOT / relative
|
||||
target = Path(tmpdir) / relative.replace("/", "-")
|
||||
shutil.copytree(source, target)
|
||||
return target
|
||||
|
||||
|
||||
def issue_codes(payload: dict[str, object]) -> set[str]:
|
||||
issues = payload.get("issues")
|
||||
if not isinstance(issues, list):
|
||||
return set()
|
||||
return {item.get("code") for item in issues if isinstance(item, dict) and isinstance(item.get("code"), str)}
|
||||
|
||||
|
||||
class SVGlideThemeSystemP0FixtureTest(unittest.TestCase):
|
||||
def test_evidence_file_exists_and_keeps_p0_scope(self) -> None:
|
||||
self.assertTrue(EVIDENCE_PATH.exists(), f"missing evidence file: {EVIDENCE_PATH}")
|
||||
text = EVIDENCE_PATH.read_text(encoding="utf-8")
|
||||
for required in ["P0-0", "P0-8", "theme_validate", "theme_adherence", "pre_submit_review", "direct_svg"]:
|
||||
self.assertIn(required, text)
|
||||
for forbidden in ["自动审美已通过", "已支持完整主题系统", "PPTX export 已完成"]:
|
||||
self.assertNotIn(forbidden, text)
|
||||
|
||||
def test_positive_artboard_fixture_validates_theme_and_final_svg(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
project = copy_fixture("theme-system-p0/artboard-satori", tmpdir)
|
||||
|
||||
validation = svglide_theme_validate.validate_project(project)
|
||||
svglide_theme_validate.write_outputs(project, validation)
|
||||
adherence = svglide_theme_adherence.validate_project(project)
|
||||
|
||||
self.assertEqual(validation["status"], "passed", validation["issues"])
|
||||
self.assertEqual(adherence["status"], "passed", adherence["issues"])
|
||||
self.assertEqual(adherence["summary"]["prepared_svg_count"], 3)
|
||||
|
||||
def test_positive_direct_svg_fixture_validates_without_canvas_template(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
project = copy_fixture("theme-system-p0/direct-svg", tmpdir)
|
||||
|
||||
validation = svglide_theme_validate.validate_project(project)
|
||||
svglide_theme_validate.write_outputs(project, validation)
|
||||
adherence = svglide_theme_adherence.validate_project(project)
|
||||
|
||||
self.assertEqual(validation["status"], "passed", validation["issues"])
|
||||
self.assertEqual(adherence["status"], "passed", adherence["issues"])
|
||||
|
||||
def test_negative_fixtures_fail_for_unknown_color_low_contrast_and_stale_receipt(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
unknown = copy_fixture("theme-system-invalid/unknown-color", tmpdir)
|
||||
validation = svglide_theme_validate.validate_project(unknown)
|
||||
svglide_theme_validate.write_outputs(unknown, validation)
|
||||
unknown_result = svglide_theme_adherence.validate_project(unknown)
|
||||
|
||||
low_contrast = copy_fixture("theme-system-invalid/low-contrast", tmpdir)
|
||||
validation = svglide_theme_validate.validate_project(low_contrast)
|
||||
svglide_theme_validate.write_outputs(low_contrast, validation)
|
||||
low_contrast_result = svglide_theme_adherence.validate_project(low_contrast)
|
||||
|
||||
stale = copy_fixture("theme-system-invalid/stale-theme-validate", tmpdir)
|
||||
stale_result = svglide_theme_adherence.validate_project(stale)
|
||||
|
||||
self.assertEqual(unknown_result["status"], "failed")
|
||||
self.assertIn("theme_unknown_color", issue_codes(unknown_result))
|
||||
self.assertEqual(low_contrast_result["status"], "failed")
|
||||
self.assertIn("contrast_too_low", issue_codes(low_contrast_result))
|
||||
self.assertEqual(stale_result["status"], "failed")
|
||||
self.assertIn("theme_validate_plan_stale", issue_codes(stale_result))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||