Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
c4417db
feat(scaffold): vendor deck type layer from 02-example
tph-kds Aug 14, 2026
06a6e3b
feat(scaffold): vendor trimmed seed and command dispatch from 02-example
tph-kds Aug 14, 2026
74dd8f4
feat(scaffold): port 12 export modules from 02-example
tph-kds Aug 14, 2026
4dbc2a1
feat(scaffold): replace 21 drifted export files with 02-example versions
tph-kds Aug 14, 2026
a21fe30
refactor(scaffold): convert deck-types.ts to re-export shim over vend…
tph-kds Aug 14, 2026
1614c46
feat(scaffold): rebuild export barrel and add export-coverage audit
tph-kds Aug 14, 2026
4f7d3fd
feat(scaffold): vendor deck chart-spec from 02-example
tph-kds Aug 14, 2026
7a94d3b
chore(scaffold): sync embedded skill copy with vendored starter-compo…
tph-kds Aug 14, 2026
1ea9056
docs(scaffold): mandate snapshot export, self-contained output, and a…
tph-kds Aug 14, 2026
841e730
chore(scaffold): mirror Task 7 doc updates into embedded skill copy
tph-kds Aug 14, 2026
bcd24b2
fix(scaffold): replace fidelity-report.ts with 02-example byte-for-by…
tph-kds Aug 14, 2026
5e8b395
test(scaffold): add deck/export drift-guard wired into validate
tph-kds Aug 14, 2026
d236eb6
fix(scaffold): bind Block/SlideInteraction imports in deck-types shim
tph-kds Aug 16, 2026
0677e0e
chore(scaffold): mirror deck-types shim import fix into embedded copy
tph-kds Aug 16, 2026
73f799a
chore(validate): skip gitignored superpowers planning docs in link-check
tph-kds Aug 16, 2026
f5ac234
fix(audit): treat data: URIs as self-contained in deck asset audit
tph-kds Aug 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions examples/02-example/.agents/skills/deckforge/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,15 @@ Keep these concerns separate:

Use `starter-components/` and `examples/02-example/` as references. Adapt them instead of copying blindly.

**Runtime dependencies**

Generated decks using the scaffold export layer must install:
- `@resvg/resvg-js ^2.6.2` (SVG chart rasterization)
- `jszip ^3.10.1` and `pptxgenjs ^3.12.0` (PPTX export + verification)
- `react ^18.3.1` and `react-dom ^18.3.1`

with `"overrides": { "nanoid": "3.3.17" }`, and devDeps `typescript ^5.5.3`, `vite ^7.3.0`, `vitest ^3.2.7`, `@vitejs/plugin-react ^5.2.0`.

## 10. Run deterministic checks

The skill bundles reusable scripts in `scripts/`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ Images must occupy a meaningful visual slot. Avoid tiny decorative images surrou

Set the media `fit` explicitly (`cover` or `contain`) and choose the focal point before rendering; never stretch an image to fill a slot. Alt text is mandatory for every image, and purely decorative images must be marked as decorative so assistive technology skips them. Lazy-load below-the-fold media.

## Asset-manifest image workflow

Image imports must go through the asset manifest (`deck.assets`) plus `imageContentOf`, never through bare URLs or ad-hoc inline storage. Uploads must record the embedded pixel dimensions (`width`/`height`) on the manifest entry so cover/contain cropping uses real aspect ratios — never stretched frames. The `updateImageSource` command must keep the block binding and the manifest entry atomic so the deck stays consistent even when the source changes or fails.

## Screenshot and demo treatment

- show enough interface context to orient the audience;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ Store chart data and diagram structure as serializable content rather than scree

Check that the visual can be understood in grayscale, at presentation distance, and through its text summary. Confirm that data values match source material and that no animation changes the apparent magnitude or order of evidence.

## Export behavior

Charts use `ChartContent` with an `isTemplate` flag. Template ("New chart") charts must be excluded from export so placeholder charts never reach the rendered deck or PPTX. Process/diagram blocks use the semantic steps representation and render through the block exporters so the PPTX layout matches the browser layout.

## Data storytelling pipeline

Question and claim → data quality → comparison type → chart candidates → honest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ python scripts/audits/validate_output_contract.py <target-project> --profile <pr

Also run schema/catalog validation, type checking, tests, production build, accessibility automation, and representative visual regression when supported.

## Export quality gate

A generated app that uses the scaffold export layer must verify all of the following before completion:

1. `runExportPreflight` reports `ready: true` and zero missing assets on a fully authored deck, with no network access.
2. `makeDeckSelfContained` produces a deck whose rendered output and embedded PPTX both work offline; preflight must report `Missing: 0`.
3. PPTX export smoke-test opens the archive (via `verifyPptxArchive`) and contains non-empty `ppt/media/`.
4. Charts render through `renderChartSvg`/`renderChartRaster` (rasterized, never DOM-dependent) so PPTX chart fidelity matches the browser.

## Capability truth comes from the receipt

Regex scanning of the project is advisory only. The blocking source of truth is the capability receipt:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ def load(path:Path):
def is_remote(src:str)->bool:
return bool(re.match(r'^https?://',src.strip(),re.I))

def is_data_uri(src:str)->bool:
return bool(re.match(r'^data:',src.strip(),re.I))

def frame_ratio(frame)->float|None:
w=frame.get('w');h=frame.get('h')
if not w or not h:return None
Expand Down Expand Up @@ -70,7 +73,7 @@ def main():
errors.append(f'{sid}/{bid}: image has no source');item['status']='error'
elif asset and not asset.get('src'):
errors.append(f'{sid}/{bid}: asset "{asset_id}" is remote-only (no local source)');item['status']='error'
elif src and not is_remote(src):
elif src and not is_remote(src) and not is_data_uri(src):
local=Path(str(args.deck).rsplit('/',1)[0])/src
if not local.exists():
errors.append(f'{sid}/{bid}: local asset file missing: {src}');item['status']='error'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Audit that export/index.ts re-exports every public symbol of the scaffold export/ modules."""
from __future__ import annotations

import re
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parents[3]
BARREL = ROOT / "skills" / "deckforge" / "starter-components" / "export" / "index.ts"
EXPORT_DIR = BARREL.parent
MODULES = ("export-types.ts", "export-preflight.ts", "export-dialog.tsx",
"snapshot.ts", "prepare-export.ts", "self-contained.ts",
"resolved-theme.ts", "geometry.ts", "image-dimensions.ts",
"export-scene.ts", "fidelity/content-parity.ts",
"fidelity/fidelity-policy.ts", "fidelity/fidelity-report.ts",
"fidelity/fidelity-types.ts", "fidelity/representation-planner.ts",
"fidelity/svg/svg-chart.ts", "fidelity/svg/svg-diagram.ts",
"fidelity/svg/svg-raster.ts", "fidelity/svg/svg-snapshot.ts",
"pptx/pptx-exporter.ts", "pptx/pptx-verifier.ts", "pptx/pptx-context.ts",
"pptx/pptx-theme.ts", "pptx/pptx-fonts.ts", "pptx/pptx-assets.ts",
"pptx/pptx-fallback-renderer.ts", "pptx/pptx-placeholder.ts",
"pptx/export-utils.ts", "pptx/block-exporters/chart.ts",
"pptx/block-exporters/diagram.ts", "pptx/block-exporters/fallback.ts",
"pptx/block-exporters/image.ts", "pptx/block-exporters/index.ts",
"pptx/block-exporters/process.ts", "pptx/block-exporters/shape.ts",
"pptx/block-exporters/table.ts", "pptx/block-exporters/text.ts",
"pptx/block-exporters/video.ts")

SYMBOL = re.compile(r"^export\s+(?:type\s+)?(?:declare\s+)?(?:abstract\s+)?(?:async\s+)?(?:function|const|class|interface|type|enum|var|let)\s+([A-Za-z_$][\w$]*)", re.MULTILINE)

def exported_symbols(path: Path) -> set[str]:
return {m.group(1) for m in SYMBOL.finditer(path.read_text(encoding="utf-8"))}

def main() -> int:
barrel_text = BARREL.read_text(encoding="utf-8")
missing: list[str] = []
for rel in MODULES:
for sym in exported_symbols(EXPORT_DIR / rel):
if not re.search(rf"\b{re.escape(sym)}\b", barrel_text):
missing.append(f"{rel}: {sym}")
if missing:
print("Barrel is missing exports for:")
for item in missing:
print(f" {item}")
return 1
print(f"OK: barrel re-exports all symbols from {len(MODULES)} modules")
return 0

if __name__ == "__main__":
sys.exit(main())
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ Included references cover:
- deterministic content measurement (overflow, collision, budget, boundary, orphan) and its repair pass (move/trim/truncate with a fixed-point loop).

A production implementation must still adapt authentication, API persistence, collaboration, rich-text/media adapters, schema validation, authorization, asset upload, visual regression, and runtime security to the target product.

**Runtime dependencies**

Generated decks using the scaffold export layer must install:
- `@resvg/resvg-js ^2.6.2` (SVG chart rasterization)
- `jszip ^3.10.1` and `pptxgenjs ^3.12.0` (PPTX export + verification)
- `react ^18.3.1` and `react-dom ^18.3.1`

with `"overrides": { "nanoid": "3.3.17" }`, and devDeps `typescript ^5.5.3`, `vite ^7.3.0`, `vitest ^3.2.7`, `@vitejs/plugin-react ^5.2.0`.
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { Block, SlideInteraction } from './deck/types';

export type DeckId = string;
export type SlideId = string;
export type BlockId = string;
export type InteractionId = string;

export type Frame = { x: number; y: number; w: number; h: number; rotation?: number; z?: number };
export type PositionMode = 'slot' | 'flow' | 'freeform' | 'background';

export type BuildAnimation = {
id: string;
trigger?: 'on-enter' | 'on-click' | 'with-previous' | 'after-previous' | 'on-hover' | 'on-visible';
Expand All @@ -16,135 +15,43 @@ export type BuildAnimation = {
reducedMotionFallback?: string;
};

export type DeckBlock = {
id: BlockId;
type: string;
content?: unknown;
slot?: string;
positionMode?: PositionMode;
frame?: Frame;
resolvedFrame?: Frame;
fitPolicy?: 'wrap' | 'contain' | 'cover' | 'scroll' | 'change-layout' | 'split-slide';
style?: Record<string, unknown>;
data?: unknown;
alt?: string;
ariaLabel?: string;
sourceIds?: string[];
animation?: BuildAnimation;
locked?: boolean;
hidden?: boolean;
decorative?: boolean;
allowOverlap?: boolean;
groupId?: string;
role?: string;
};

export type LayoutBinding = { slot: string; blockIds: BlockId[]; flow?: 'stack' | 'row' | 'grid' | 'overlay'; gap?: number };

export type Block = DeckBlock;
export type DeckBlock = Block;

export type DeckInteraction = {
id: InteractionId;
type: string;
trigger: string;
targetId?: string;
action: string;
payload?: unknown;
export type DeckInteraction = SlideInteraction & {
audienceVisible?: boolean;
requiresNetwork?: boolean;
fallback?: string;
ariaLabel?: string;
};

export type DeckSlide = {
id: SlideId;
title: string;
layout: string;
layoutVariant?: string;
layoutBindings?: LayoutBinding[];
density?: 'low' | 'medium' | 'high';
focalBlockId?: BlockId;
blocks: DeckBlock[];
speakerNotes?: string;
sources?: string[];
interactions?: DeckInteraction[];
hidden?: boolean;
section?: string;
transition?: string;
durationMs?: number;
};

export type DeckProject = {
schemaVersion: '2.1';
experience: {
profile: 'editable-deck' | 'presentation-runtime' | 'published-story' | 'embedded-deck';
surfaces: Array<'editor' | 'presenter' | 'viewer' | 'embed-viewer'>;
routes?: Record<string, string>;
capabilities?: string[];
};
meta: {
id: DeckId;
slug: string;
title: string;
language: string;
description?: string;
audience?: string;
objective?: string;
templateId?: string;
};
canvas: {
aspectRatio: '16:9' | '4:3' | 'custom';
width: number;
height: number;
safeMargin?: number;
grid?: number;
responsiveMode?: 'letterbox' | 'reflow' | 'hybrid';
layoutMode?: 'semantic-slots' | 'hybrid' | 'freeform';
};
theme: { id: string; overrides?: Record<string, unknown>; designSystemRef?: string };
presentation: {
mode: 'horizontal' | 'vertical' | 'freeform' | '3d-coverflow';
transition: string;
keyboard: boolean;
touch?: boolean;
deepLinks?: boolean;
overview?: boolean;
speakerView?: boolean;
progress?: boolean;
controls?: boolean;
reducedMotion: 'respect-system' | 'always' | 'never';
motionProfileId?: string;
defaultBuilds?: boolean;
};
editor: {
enabled: boolean;
toolbar: boolean;
history: boolean;
sidePanel?: boolean;
assetLibrary?: boolean;
themePicker?: boolean;
layoutPicker?: boolean;
shortcutHelp?: boolean;
saveStatus?: boolean;
persistence?: 'none' | 'local-storage' | 'api' | 'host-managed';
snapToGrid?: boolean;
guides?: boolean;
comments?: boolean;
collaboration?: boolean;
autosave?: boolean;
commandPalette?: boolean;
notes?: boolean;
allowedBlockTypes?: string[];
requiredZones?: string[];
};
shortcuts?: { helpEnabled?: boolean; helpKey?: string; editorPreset?: string; presenterPreset?: string };
slides: DeckSlide[];
sources?: Array<{ id: string; title: string; url: string }>;
publish: { visibility: 'private' | 'workspace' | 'unlisted' | 'public'; embed: { enabled: boolean; allowedOrigins?: string[]; sandbox?: string[]; responsive?: boolean } };
};

export type EditorSelection = { slideId: SlideId; blockIds: BlockId[]; mode?: 'block' | 'text' | 'canvas' };
export type SaveState = 'clean' | 'dirty' | 'saving' | 'saved' | 'failed' | 'offline' | 'conflict';

export type {
PositionMode,
FitPolicy,
Frame,
BlockAnimation,
BlockStyle,
ChartValue,
ChartContent,
AssetKind,
DeckAsset,
ImageBlockContent,
MetricContent,
ProcessStep,
Block,
LayoutBinding,
SlideInteraction,
DeckSlide,
SourceRef,
ThemeTokens,
ThemeGradients,
ThemeDef,
DeckProject,
SaveState,
Route,
PresenterBuildState,
RenderBlockProps,
} from './deck/types';

export type {
ExportIssueSeverity,
Expand Down
Loading
Loading