From 10ba1c558961428e1c1ac47ec0a371b3937202b5 Mon Sep 17 00:00:00 2001 From: Leo Farias Date: Wed, 8 Jul 2026 10:58:10 -0400 Subject: [PATCH 1/4] fix: add missing newline at end of .fvmrc and settings.json files --- .fvmrc | 2 +- .vscode/settings.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.fvmrc b/.fvmrc index 8ab3a251..457360f8 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { "flutter": "3.44.0" -} +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 921c45b0..871ef0d4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,4 +8,4 @@ "**/.fvm/versions": true, "**/.fvm/flutter_sdk": true } -} +} \ No newline at end of file From 7722587ebebf89dca853aceb2974772562e092da Mon Sep 17 00:00:00 2001 From: Leo Farias Date: Wed, 8 Jul 2026 13:09:12 -0400 Subject: [PATCH 2/4] feat: add ack generated widget example --- .../skills/superdeck-presentations/SKILL.md | 61 +++ .../agents/openai.yaml | 4 + .../references/authoring.md | 442 ++++++++++++++++++ .../references/runtime-customization.md | 425 +++++++++++++++++ .../references/verification.md | 147 ++++++ demo/.superdeck/build_status.json | 4 +- demo/.superdeck/superdeck.json | 55 +++ demo/.superdeck/superdeck_full.json | 130 ++++++ demo/lib/src/widgets/ack_metric_card.dart | 80 ++++ demo/lib/src/widgets/ack_metric_card.g.dart | 34 ++ demo/lib/src/widgets/demo_widgets.dart | 7 + demo/pubspec.yaml | 4 + demo/slides.md | 40 ++ demo/test/ack_metric_card_test.dart | 30 ++ melos.yaml | 2 +- packages/core/pubspec.yaml | 2 +- packages/playground/pubspec.yaml | 8 +- 17 files changed, 1467 insertions(+), 8 deletions(-) create mode 100644 .agents/skills/superdeck-presentations/SKILL.md create mode 100644 .agents/skills/superdeck-presentations/agents/openai.yaml create mode 100644 .agents/skills/superdeck-presentations/references/authoring.md create mode 100644 .agents/skills/superdeck-presentations/references/runtime-customization.md create mode 100644 .agents/skills/superdeck-presentations/references/verification.md create mode 100644 demo/lib/src/widgets/ack_metric_card.dart create mode 100644 demo/lib/src/widgets/ack_metric_card.g.dart create mode 100644 demo/test/ack_metric_card_test.dart diff --git a/.agents/skills/superdeck-presentations/SKILL.md b/.agents/skills/superdeck-presentations/SKILL.md new file mode 100644 index 00000000..f5bbd68d --- /dev/null +++ b/.agents/skills/superdeck-presentations/SKILL.md @@ -0,0 +1,61 @@ +--- +name: superdeck-presentations +description: Create, review, or edit SuperDeck presentations and presentation apps. Use when working with `slides.md`, SuperDeck Markdown syntax, `@section`/`@block`/`@widget` layout, built-in widgets such as `@image`, `@dartpad`, and `@qrcode`, custom Flutter widgets, `DeckOptions`, slide parts, templates, styles, images/assets, CLI builds, plugins, or validation of SuperDeck deck behavior. +--- + +# SuperDeck Presentations + +## Overview + +Use this skill to build accurate SuperDeck presentations: Markdown slide content, Flutter runtime wiring, assets, custom widgets, styling, templates, and verification. + +## Reference Routing + +Load only the reference needed for the task: + +- Read `references/authoring.md` when creating or reviewing `slides.md`, block syntax, layouts, slide notes, built-in widgets, Markdown images, alerts, code, or hero markers. +- Read `references/runtime-customization.md` when wiring a Flutter app, registering custom widgets, configuring `DeckOptions`, slide parts, styles, templates, assets, or plugins. +- Read `references/verification.md` before claiming a deck/app works, when choosing commands, or when diagnosing build/render issues. + +## Workflow + +1. Inspect the existing `slides.md`, `lib/main.dart`, `pubspec.yaml`, and any registered widgets/parts/templates before editing. +2. Decide whether the task is authoring-only, runtime customization, plugin/build setup, or verification/debugging; load the matching reference. +3. Prefer the repository's documented syntax and implementation behavior over generic presentation assumptions. +4. Keep slides readable in Markdown: use frontmatter for metadata, `@section` for vertical rows, child blocks/widgets for horizontal columns, and `flex` ratios for sizing. +5. Verify with a real SuperDeck build or targeted tests before reporting success. + +## Ground Truths + +- SuperDeck renders a 1280x720 logical slide into a 16:9 scaled viewport. +- A slide contains vertical sections. Each `@section` starts a new vertical row; blocks inside that section are laid out horizontally. +- `@block` renders Markdown. `@widget` and any unrecognized `@name` render a `WidgetBlock`. +- Images have two authoring paths. Prefer standalone Markdown `![alt](src)` when the image belongs in the Markdown content flow; use `@image { src: ... }` when the image needs block-level layout control such as `fit`, fixed size, `flex`, `align`, `scrollable`, or `data:` source support. +- `@column` is intentionally unsupported; use `@block`. +- Block and widget `align` controls visible content alignment. Current renderer stores `section.align` but does not apply it to child layout; set `align` on child blocks/widgets when visual placement matters. +- `scrollable` is valid on `@block` and widget blocks, not on `@section`. +- Built-ins `image`, `dartpad`, and `qrcode` are always registered and can be overridden by user widgets with the same name. +- `@dartpad` is the built-in WebView-backed widget; there is no separate built-in `@webview` authoring tag. +- Custom widgets must be registered in `DeckOptions.widgets`; use shorthand `@widgetName { ... }` in `slides.md` for registered widget names. +- Styles, templates, widgets, slide parts, and plugins are configured in Dart through `DeckOptions`/`SuperDeckApp`, not through a separate `styles.yaml`. +- The CLI reads `slides.md`, writes `.superdeck/superdeck.json`, and ensures `.superdeck/` is listed in Flutter assets unless `--skip-pubspec` is used. + +## Source Map + +Use these files to resolve disputes or update this skill: + +- `docs/guides/markdown-authoring.mdx` +- `docs/reference/markdown-syntax.mdx` +- `docs/reference/block-types.mdx` +- `docs/reference/deck-options.mdx` +- `docs/guides/custom-widgets.mdx` +- `docs/guides/slide-parts.mdx` +- `docs/guides/cli-reference.mdx` +- `packages/builder/lib/src/parsers/markdown_parser.dart` +- `packages/builder/lib/src/parsers/section_parser.dart` +- `packages/core/lib/src/deck/block_model.dart` +- `packages/superdeck/lib/src/rendering/slides/slide_view.dart` +- `packages/superdeck/lib/src/rendering/blocks/block_widget.dart` +- `packages/superdeck/lib/src/builtins/` +- `packages/superdeck/lib/src/ui/widgets/webview_wrapper.dart` +- `packages/playground/lib/features/ai/quick_agent/core/engine/schemas/deck_schemas.dart` diff --git a/.agents/skills/superdeck-presentations/agents/openai.yaml b/.agents/skills/superdeck-presentations/agents/openai.yaml new file mode 100644 index 00000000..5e1c5a84 --- /dev/null +++ b/.agents/skills/superdeck-presentations/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "SuperDeck Presentations" + short_description: "Build SuperDeck Markdown presentations" + default_prompt: "Use $superdeck-presentations to create or refine a SuperDeck slides.md presentation." diff --git a/.agents/skills/superdeck-presentations/references/authoring.md b/.agents/skills/superdeck-presentations/references/authoring.md new file mode 100644 index 00000000..12dfa4e9 --- /dev/null +++ b/.agents/skills/superdeck-presentations/references/authoring.md @@ -0,0 +1,442 @@ +# Authoring SuperDeck Slides + +Use this reference when editing `slides.md` or explaining SuperDeck Markdown behavior. + +## Slide Document Structure + +Create `slides.md` at the Flutter project root. SuperDeck splits slides on standalone `---` lines outside fenced code blocks. + +Prefer frontmatter for every slide: + +```markdown +--- +title: Product Vision +style: hero +template: keynote +owner: Platform Team +--- + +@block { align: center } + +# Product Vision + +--- +title: Next Slide +--- + +# Plain Markdown also works +``` + +Supported frontmatter keys: + +- `title`: Used by navigation/export/chrome. +- `style`: Named style from `DeckOptions.styles`, or from the active template's `styles`. +- `template`: Named `DeckOptions.templates` entry. Use `template: none` to opt out of `defaultTemplate`. +- Any other key: Preserved in `SlideOptions.args`. + +Plain slides without frontmatter are valid: + +```markdown +# Slide 1 + +--- + +# Slide 2 +``` + +HTML comments become speaker notes in `Slide.comments`: + +```markdown + +``` + +Multiline comments are normalized into a single note string. Comments also remain embedded in the slide content, where Markdown rendering keeps them invisible. + +## Directive Syntax + +Directives are line-starting tags outside fenced code blocks: + +```markdown +@block + +@image { src: assets/logo.png } + +@widget { + name: "metricCard" + value: 42 +} +``` + +Options are strict YAML inside balanced braces. Prefer multiline options when there is more than one key: + +```markdown +@block { + flex: 2 + align: topLeft + scrollable: true +} +``` + +Values can be strings, numbers, booleans, lists, or objects. Quote strings that contain punctuation, spaces, `#`, `:`, or YAML keywords such as `on`, `off`, `true`, `false`, or `null`. + +If visible Markdown content must begin with `@`, write `_@channel`; the parser restores it to `@channel` without treating it as a directive. + +Do not use `@column`; it throws a format error. Use `@block`. + +## Layout Model + +SuperDeck renders slides at a logical `1280 x 720` resolution and scales to the viewport. + +Sections stack vertically: + +```markdown +@section { flex: 1 } +@block +Header row + +@section { flex: 3 } +@block +Main left +@block +Main right + +@section { flex: 1 } +@block +Footer row +``` + +Blocks inside each section are laid out horizontally. `flex` controls relative width for blocks and relative height for sections. + +If a slide has no directives, the full slide content becomes one default content block in one default section. + +If `@block` directives appear without an explicit `@section`, SuperDeck creates a default section and places the blocks side by side: + +```markdown +@block { flex: 2 } +Left content + +@block { flex: 1 } +Right content +``` + +Use `align` on child blocks or widget blocks for visible alignment: + +```markdown +@block { align: center } + +# Centered title +``` + +Valid alignments are `topLeft`, `topCenter`, `topRight`, `centerLeft`, `center`, `centerRight`, `bottomLeft`, `bottomCenter`, `bottomRight`. + +Current renderer note: `@section { align: ... }` is accepted and stored, but visible content placement is driven by each child block/widget's `align`. + +Use `scrollable: true` on overflowing blocks/widgets, not on sections: + +```markdown +@block { + scrollable: true +} + +Long content... +``` + +## Built-In Widgets + +Built-ins use widget block behavior, so `flex`, `align`, and `scrollable` can be used alongside widget-specific arguments. + +### Images + +Choose image syntax by intent: + +- Prefer standalone Markdown image syntax when the image is part of the slide's written content, for example an illustration after a paragraph, a small diagram in a text-heavy block, or an image that should stay near surrounding Markdown. +- Prefer `@image` when the image is a designed slide element, for example a hero visual, a dedicated column, a screenshot that must use `cover`/`contain`, a fixed-size logo, or a source that needs `data:` URI support. +- Do not place Markdown images inline inside a text sentence; the current renderer supports standalone Markdown images. + +Use Markdown image syntax when the image belongs inside Markdown content: + +```markdown +![Architecture](assets/architecture.png) +``` + +Use `@image` when the image should be its own widget block/column, or when you need fit, explicit sizing, flex, alignment, or scrolling controls: + +```markdown +@image { + src: assets/hero.png + fit: cover + height: 420 + align: center +} +``` + +`@image` arguments: + +- `src` required: relative asset path, URL, absolute path, `file://`, `data:`, or Windows absolute path. +- `fit`: `fill`, `contain`, `cover`, `fitWidth`, `fitHeight`, `none`, `scaleDown`; default `contain`. +- `width`, `height`: positive logical pixels. + +Key differences: + +| Syntax | Where it lives | Best for | Source handling | +|---|---|---|---| +| `![Alt](src)` | Markdown/plain slide content or inside `@block` content, on its own line | Content-flow images, text-adjacent diagrams, simple standalone images, hero-marked Markdown images | Validates sources defensively: relative, `http`, `https`, and `file`; rejects path traversal | +| `@image { src: ... }` | Widget block (`WidgetBlock`) | Dedicated image columns, hero visuals, exact fit/size, `flex`, `align`, `scrollable`, `data:` images | Accepts author-controlled asset paths, URLs, absolute/file paths, `data:` URIs, and Windows absolute paths | + +Markdown images can use hero markers: + +```markdown +![Architecture](assets/architecture.png) {.hero-architecture} +``` + +### DartPad + +Use `@dartpad` for live Dart or Flutter examples that the audience should run, edit, or inspect during the presentation. + +```markdown +@dartpad { + id: "d7b09149b0843f2b9d09e081e3cfd5a3" + theme: dark + run: true +} +``` + +Arguments: + +- `id` required. +- `theme`: `light` or `dark`. +- `embed`: boolean, default `true`. +- `run`: boolean, default `true`. + +DartPad renders through SuperDeck's internal `WebViewWrapper`; there is no separate built-in `@webview` tag. Verify the target platform supports WebViews and can reach `https://dartpad.dev`. + +Share a DartPad snippet through a GitHub Gist: + +1. Create a GitHub Gist containing a `main.dart` file. +2. Copy the gist ID from the gist URL, without the username. +3. Test it with `https://dartpad.dev/?id=`. +4. Put that same gist ID in the SuperDeck block: + +```markdown +@dartpad { + id: "5c0e154dd50af4a9ac856908061291bc" + theme: dark + embed: true + run: true +} +``` + +Use `run: false` when you want the audience to read or edit before executing. Use `theme: dark` for dark slide styles and `theme: light` for lighter decks. Avoid relying on DartPad's old in-app share/update flow; current sharing is gist-based. + +Runtime behavior to remember: + +- SuperDeck builds `https://dartpad.dev/?id=&theme=&embed=&run=` from the `@dartpad` args. +- The WebView uses unrestricted JavaScript because DartPad needs it. +- The wrapper blocks navigation away from the original host, so links to other domains will not navigate inside the embedded view. +- The WebView is hidden until the page finishes loading, then fades in after a short delay. +- The wrapper overlays refresh and clear-editor controls. +- State is kept alive while the slide tree keeps the widget alive; changing the DartPad URL reloads the WebView. + +### QR Code + +Use `@qrcode` when a slide needs a scannable link or text payload, for example a demo URL, docs link, feedback form, repository, event page, or contact handoff. Keep QR slides visually simple: pair the QR code with a short label or URL in a nearby `@block`, and verify the code scans at presentation distance. + +```markdown +@qrcode { + text: "https://superdeck.dev" + size: 220 + errorCorrection: high + backgroundColor: "#ffffff" + foregroundColor: "#000000" +} +``` + +Arguments: + +- `text` required, max 1000 characters. +- `size`: 1 through 1000, default `200`. +- `errorCorrection`: `low`/`l`, `medium`/`m`, `high`/`q`, or `highest`/`h`; default `medium`. +- `backgroundColor`, `foregroundColor`: hex colors. + +QR examples: + +```markdown +@section + +@block { + flex: 2 + align: center +} + +## Try the demo +https://superdeck-dev.web.app + +@qrcode { + text: "https://superdeck-dev.web.app" + size: 260 + errorCorrection: high + backgroundColor: "#ffffff" + foregroundColor: "#111827" + flex: 1 + align: center +} +``` + +Use `errorCorrection: high` or `highest` when the QR code may be printed, projected, or placed near visual noise. Avoid very long text payloads; the widget rejects text over 1000 characters and dense QR codes are harder to scan. + +## Custom Widgets in Markdown + +Use custom widgets when the slide needs something Markdown cannot express cleanly: live app UI, charts, counters, embeds, animations, product screenshots with interaction, domain-specific cards, or reusable branded components. + +In `slides.md`, use shorthand for registered widgets: + +```markdown +@metricCard { + label: Activation + value: "72%" + trend: up +} +``` + +Or explicit form: + +```markdown +@widget { + name: "metricCard" + label: Activation + value: "72%" +} +``` + +All properties become the widget factory's `Map` arguments. Block-level controls such as `flex`, `align`, and `scrollable` are consumed by SuperDeck and are not passed as custom widget args. + +Custom widget authoring rules: + +- Register the widget name in `DeckOptions.widgets` before using it. +- Prefer shorthand `@metricCard { ... }` for normal widgets. +- Use explicit `@widget { name: "metricCard" ... }` when generating generic widget blocks or when the widget name is dynamic. +- Quote strings with punctuation, spaces, or YAML-sensitive values. +- Parse and validate args in the Flutter widget/factory; SuperDeck does not know your custom schema. +- Use `scrollable: true` on the widget block when the rendered widget may exceed its block height. +- Use `LayoutBuilder` inside the widget to adapt to the block's constraints. + +Any unrecognized `@name` becomes a `WidgetBlock` with `name: name`. `section`, `block`, `widget`, and `column` are reserved directive names. + +Custom widget layout example: + +```markdown +@section + +@block { flex: 2 } +## Activation +The north-star metric improved after onboarding changes. + +@metricCard { + label: Activation + value: "72%" + trend: up + flex: 1 + align: center +} +``` + +## Markdown Features + +SuperDeck uses GitHub-flavored Markdown plus custom builders: + +- Headings, paragraphs, emphasis, lists, task lists, tables, blockquotes, links. +- Code blocks with highlighting for `dart`, `json`, `yaml`, `markdown`, `python`, and `mermaid`; unknown languages fall back to Dart highlighting. +- GitHub alerts: + +```markdown +> [!NOTE] +> Useful context. + +> [!WARNING] +> Important risk. +``` + +Supported alert labels include `NOTE`, `TIP`, `IMPORTANT`, `WARNING`, and `CAUTION`. + +Hero markers can be attached to headings, images, and fenced code blocks: + +````markdown +# Roadmap {.hero-title} +![Diagram](assets/roadmap.png) {.hero-visual} + +```dart {.hero-code} +void main() {} +``` +```` + +The first valid class name is used as the hero tag. Do not rely on classes beginning with `--`; they are rejected. + +## Authoring Patterns + +Title slide: + +```markdown +--- +title: Launch Plan +style: hero +--- + +@block { align: center } + +# Launch Plan +## Q3 execution narrative +``` + +Text plus visual: + +```markdown +--- +title: Why Now +--- + +@block { flex: 2 } +## Market pressure +- Buyers expect immediate insight +- Competitors are bundling workflows +- Internal data quality is now sufficient + +@image { + src: assets/market-map.png + fit: contain + flex: 3 +} +``` + +Three columns: + +```markdown +@section + +@block +### Problem +Fragmented workflows + +@block +### Move +Unified deck authoring + +@block +### Proof +Live Flutter components +``` + +Vertical rows: + +```markdown +@section { flex: 1 } +@block { align: center } +## Executive summary + +@section { flex: 4 } +@block +Main content + +@section { flex: 1 } +@block { align: bottomRight } +Footer note +``` diff --git a/.agents/skills/superdeck-presentations/references/runtime-customization.md b/.agents/skills/superdeck-presentations/references/runtime-customization.md new file mode 100644 index 00000000..d8d9ffe3 --- /dev/null +++ b/.agents/skills/superdeck-presentations/references/runtime-customization.md @@ -0,0 +1,425 @@ +# Runtime Customization + +Use this reference when wiring a SuperDeck Flutter app, assets, styles, templates, custom widgets, slide parts, or plugins. + +## Minimal App Setup + +SuperDeck apps must initialize before `runApp`: + +```dart +import 'package:flutter/widgets.dart'; +import 'package:superdeck/superdeck.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await SuperDeckApp.initialize(); + + runApp(const SuperDeckApp(options: DeckOptions())); +} +``` + +The CLI expects `slides.md` at the project root. Build output lives in `.superdeck/`. + +Typical setup: + +```bash +dart pub global activate superdeck_cli +flutter create my_presentation +cd my_presentation +superdeck setup +flutter pub add superdeck +flutter pub add --dev superdeck_cli +dart run superdeck_cli:main build --watch +flutter run +``` + +`superdeck setup` creates `.superdeck/`, adds `.superdeck/` to `flutter.assets`, and patches macOS entitlements when a macOS runner exists. + +## DeckOptions + +`DeckOptions` configures the runtime: + +```dart +DeckOptions( + baseStyle: myBaseStyle, + styles: { + 'quote': quoteStyle(), + }, + widgets: { + 'metricCard': MetricCard.new, + }, + parts: const SlideParts( + header: DeckHeader(), + footer: DeckFooter(), + background: DeckBackground(), + ), + templates: { + 'brand': SlideTemplate( + parts: SlideParts(header: BrandHeader()), + baseStyle: brandStyle, + styles: {'cover': coverStyle}, + ), + }, + defaultTemplate: SlideTemplate(parts: SlideParts(footer: BrandFooter())), + debug: true, +) +``` + +There is no `styles.yaml`; styles and templates are Dart code. + +## Custom Widgets + +A widget factory is `Widget Function(Map args)`. + +```dart +import 'package:flutter/widgets.dart'; +import 'package:superdeck/superdeck.dart'; + +class MetricCard extends StatelessWidget { + final String label; + final String value; + + MetricCard(Map args, {super.key}) + : label = args['label'] as String? ?? '', + value = args['value'] as String? ?? ''; + + @override + Widget build(BuildContext context) { + return Center(child: Text('$label: $value')); + } +} + +SuperDeckApp( + options: DeckOptions( + widgets: { + 'metricCard': MetricCard.new, + }, + ), +); +``` + +For non-trivial widgets, parse arguments into a typed shape and validate early. `Ack` is available from `package:superdeck_core/superdeck_core.dart`. + +Widget blocks can read slide context: + +```dart +final slide = SlideConfiguration.of(context); +final title = slide.options.title; +final index = slide.slideIndex; +final args = slide.options.args; +``` + +Use Flutter's `LayoutBuilder` for sizing. SuperDeck does not export a public `MeasureSize` API. + +If a widget factory is missing, SuperDeck renders `Widget not found: `. If the factory throws, SuperDeck renders error details on the slide. + +Built-ins `image`, `dartpad`, and `qrcode` are registered first. User widgets with the same name override a built-in only when that name is used by the slide. + +### Recommended Custom Widget Pattern + +Use a typed args object for anything beyond trivial text. This keeps slide authoring errors readable because SuperDeck catches factory exceptions and renders them on the slide. + +```dart +import 'package:flutter/widgets.dart'; +import 'package:superdeck/superdeck.dart'; +import 'package:superdeck_core/superdeck_core.dart'; + +class MetricCardArgs { + const MetricCardArgs({ + required this.label, + required this.value, + this.trend, + }); + + final String label; + final String value; + final String? trend; + + static final schema = Ack.object({ + 'label': Ack.string().notEmpty(), + 'value': Ack.string().notEmpty(), + 'trend': Ack.string().optional(), + }); + + static MetricCardArgs parse(Map args) { + schema.parse(args); + return MetricCardArgs( + label: args['label'] as String, + value: args['value'] as String, + trend: args['trend'] as String?, + ); + } +} + +class MetricCard extends StatelessWidget { + final MetricCardArgs data; + + MetricCard(Map args, {super.key}) + : data = MetricCardArgs.parse(args); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + return SizedBox( + width: constraints.maxWidth, + height: constraints.maxHeight, + child: Center(child: Text('${data.label}: ${data.value}')), + ); + }, + ); + } +} +``` + +### Ack-Generated Args Wrappers + +Ack can also generate typed extension wrappers over a validated `Map`. This helps when you want typed getters without maintaining a manual args class. It does not change SuperDeck's widget factory contract; the factory still receives `Map args`. + +Use this pattern only when the target app already has Ack codegen configured, or when you are intentionally adding it: + +```bash +dart pub add ack ack_annotations +dart pub add --dev ack_generator build_runner +``` + +Keep Ack package versions aligned with the app's existing dependency policy. In the SuperDeck repo, match the pinned Ack versions in the package you are editing; playground schemas show the pattern in `packages/playground/lib/features/ai/quick_agent/core/engine/schemas/deck_schemas.dart`. + +```dart +import 'package:ack/ack.dart'; +import 'package:ack_annotations/ack_annotations.dart'; +import 'package:flutter/widgets.dart'; +import 'package:superdeck/superdeck.dart'; + +part 'metric_card.g.dart'; + +@AckType(name: 'MetricCardArgs') +final metricCardArgsSchema = Ack.object({ + 'label': Ack.string().notEmpty(), + 'value': Ack.string().notEmpty(), + 'trend': Ack.string().optional(), +}); + +class MetricCard extends StatelessWidget { + final MetricCardArgsType data; + + MetricCard(Map args, {super.key}) + : data = MetricCardArgsType.parse(args); + + @override + Widget build(BuildContext context) { + return Center(child: Text('${data.label}: ${data.value}')); + } +} +``` + +Generate the wrapper after schema changes: + +```bash +dart run build_runner build --delete-conflicting-outputs +``` + +Ack generation constraints that matter for SuperDeck widgets: + +- Annotate top-level schema variables or getters with `@AckType()`. +- Generated extension types implement `Map` and expose `parse`, `safeParse`, and typed getters. +- Nested object fields should reference named top-level schemas when you need typed nested getters. +- Do not expect `Ack.any()`/`Ack.anyOf()` or inline anonymous object branches to generate useful typed wrappers. +- Keep `align`, `flex`, `scrollable`, and `name` out of widget-specific schemas because SuperDeck consumes those reserved block keys before calling the factory. + +Register it: + +```dart +SuperDeckApp( + options: DeckOptions( + widgets: { + 'metricCard': MetricCard.new, + }, + ), +); +``` + +Use it: + +```markdown +@metricCard { + label: Activation + value: "72%" + trend: up + align: center +} +``` + +Notes: + +- `align`, `flex`, `scrollable`, and `name` are reserved block keys; do not expect them inside `args`. +- Widget names can include letters, digits, underscores, and hyphens because directive tags match `@[\w-]+`. +- A custom widget can override a built-in by registering `image`, `dartpad`, or `qrcode`, but do that only intentionally. +- Use `SlideConfiguration.of(context)` for slide metadata and `LayoutBuilder` for block size. + +## Slide Parts + +Use slide parts for shared chrome: + +- Header: `PreferredSizeWidget` +- Footer: `PreferredSizeWidget` +- Background: `Widget` + +```dart +class DeckHeader extends StatelessWidget implements PreferredSizeWidget { + const DeckHeader({super.key}); + + @override + Size get preferredSize => const Size.fromHeight(56); + + @override + Widget build(BuildContext context) { + final slide = SlideConfiguration.of(context); + return Align( + alignment: Alignment.centerLeft, + child: Text(slide.options.title ?? ''), + ); + } +} +``` + +Header and footer consume vertical space from the 1280x720 slide content area. Background fills the full slide. + +## Styles and Templates + +Slide frontmatter selects styles/templates: + +```markdown +--- +style: cover +template: brand +--- +``` + +Resolution order: + +- With a template: `defaultSlideStyle -> template.baseStyle -> template.styles[style]`. +- Without a template: `defaultSlideStyle -> options.baseStyle -> options.styles[style]`. +- `defaultTemplate` applies when a slide has no explicit `template`. +- `template: none` opts out of `defaultTemplate`. + +Unknown templates or styles throw `ArgumentError` during configuration build, so verify names. + +Template styles are isolated. A slide using `template: brand` and `style: cover` looks up `cover` in `SlideTemplate.styles`, not `DeckOptions.styles`. + +## Images and Assets + +The CLI manages `.superdeck/` assets and ensures this entry exists unless `--skip-pubspec` is used: + +```yaml +flutter: + assets: + - .superdeck/ +``` + +Project-owned image files such as `assets/logo.png` still need to be available to Flutter: + +- Native debug runtimes can load relative paths from the filesystem. +- Web/release/static rendering falls back to Flutter `AssetImage`, so declare project asset directories in `pubspec.yaml`. +- URLs use cached network loading. +- `file://` and absolute paths are supported for author-controlled `@image` sources, but they are not portable for deployed web decks. + +Markdown images (`![Alt](src)`) use `UriValidator` and reject unsupported schemes such as `asset:`, `data:`, and path traversal (`..`) segments. `@image` parses author-controlled YAML more permissively and supports `data:` URIs through the image provider. + +Bare image keys with no scheme and no path separators, such as `slide-intro.png`, can resolve through a bound `AssetCacheStore`; this is mainly used by generated/in-memory decks. + +## Plugins + +Use custom widgets for slide content that renders directly in Flutter. Use plugins when the capability needs shell actions or build-time transforms. + +Runtime plugin example: PDF export. + +```dart +import 'package:superdeck_pdf/superdeck_pdf.dart'; + +SuperDeckApp( + options: DeckOptions(), + plugins: const [PdfPlugin()], +) +``` + +Build plugin example: Mermaid diagrams. Register `MermaidBuildPlugin` in a custom runner and run builds through that runner: + +```dart +import 'dart:io'; +import 'package:superdeck_cli/runner.dart'; +import 'package:superdeck_mermaid/superdeck_mermaid.dart'; + +Future main(List args) async { + final exitCode = await SuperDeckRunner( + plugins: [MermaidBuildPlugin()], + ).run(args); + exit(exitCode); +} +``` + +```bash +dart run tool/superdeck.dart build --watch +``` + +Plain fenced `mermaid` blocks are just code blocks unless the Mermaid build plugin transforms them into image assets. + +## DartPad Sharing + +SuperDeck's `@dartpad` widget accepts the DartPad gist ID, not a full URL. The runtime builds a URL like: + +```text +https://dartpad.dev/?id=&theme=&embed=&run= +``` + +To create a shareable DartPad for a deck: + +1. Create a GitHub Gist with a `main.dart` file. +2. Copy the gist ID from the gist URL. +3. Verify `https://dartpad.dev/?id=` loads the snippet. +4. Use that gist ID in `slides.md`: + +```markdown +@dartpad { + id: "5c0e154dd50af4a9ac856908061291bc" + theme: dark + embed: true + run: true +} +``` + +Use `run: false` for exercises where the audience should edit before running. + +## DartPad WebView Runtime + +`@dartpad` renders through `WebViewWrapper`; it is not a generic web embed API. Do not document or use `@webview` unless the app registers its own custom widget. + +Behavior: + +- Creates a `webview_flutter` `WebViewController`. +- Enables `JavaScriptMode.unrestricted` because DartPad requires JavaScript. +- Loads the generated DartPad URL from `DartPadDto.toUrl()`. +- Allows navigation only when the destination host matches the original DartPad URL host; other hosts are blocked. +- Hides the WebView initially, then fades it in after `onPageFinished` plus a 500 ms delay. +- Keeps the state alive with `AutomaticKeepAliveClientMixin`. +- Reloads when the URL changes. +- Displays overlay icon buttons for refresh and clearing the CodeMirror editor. + +Platform notes: + +- The package depends on `webview_flutter` and `webview_flutter_web`. +- `superdeck setup` patches macOS network entitlements when a macOS runner exists; run it for desktop decks using DartPad. +- Embedded DartPad needs network access to `dartpad.dev`; offline presentations should avoid relying on `@dartpad`. +- Because navigation is host-restricted, use the surrounding slide content for external links instead of expecting links inside DartPad to open other domains. + +## Deployment Notes + +SuperDeck deploys as a normal Flutter web app. For GitHub Pages, remember: + +```bash +dart run superdeck_cli:main build +flutter build web --release --base-href "//" +touch build/web/.nojekyll +``` + +The `.nojekyll` file prevents Pages from dropping Flutter's `_flutter/` directory. diff --git a/.agents/skills/superdeck-presentations/references/verification.md b/.agents/skills/superdeck-presentations/references/verification.md new file mode 100644 index 00000000..5ac7dadf --- /dev/null +++ b/.agents/skills/superdeck-presentations/references/verification.md @@ -0,0 +1,147 @@ +# Verification + +Use this reference before claiming a SuperDeck deck, runtime change, or skill update is complete. + +## For Presentation Authoring + +Run a real SuperDeck build from the target Flutter app root: + +```bash +dart run superdeck_cli:main build +``` + +For active editing: + +```bash +dart run superdeck_cli:main build --watch +flutter run +``` + +Check generated output: + +- `.superdeck/superdeck.json` exists. +- `.superdeck/build_status.json` records success. +- `pubspec.yaml` includes `.superdeck/` under `flutter.assets`, unless the project intentionally uses `--skip-pubspec`. + +If the deck uses local images, verify the paths work in the intended target: + +- Native debug: relative filesystem paths may work. +- Web/release: project assets must be declared in `pubspec.yaml`. +- Remote URLs require network access at runtime. + +Visually inspect slides when changing layout, alignment, images, custom widgets, templates, or chrome. Enable `DeckOptions(debug: true)` during layout debugging to see section/block boundaries. + +## For SuperDeck Repo Changes + +Follow the repo's FVM/Melos workflow: + +```bash +melos run build_runner:build +melos run analyze +melos run test +``` + +Use targeted tests when the change is scoped: + +```bash +cd packages/builder +fvm dart test test/src/parsers/markdown_parser_test.dart +fvm dart test test/src/parsers/section_parser_test.dart +fvm dart test test/src/parsers/block_parser_test.dart + +cd packages/superdeck +fvm flutter test test/src/rendering/slide_view_test.dart +fvm flutter test test/src/rendering/block_widget_test.dart +fvm flutter test test/src/builtins/image_widget_test.dart +fvm flutter test test/src/markdown/image_element_rendering_test.dart +``` + +Run builder/parser tests for syntax, slide splitting, comments, directives, or serialization changes. Run Flutter widget tests for rendering, alignment, image loading, slide parts, templates, and custom widget behavior. + +Regenerate generated files after model/schema changes: + +```bash +melos run build_runner:build +``` + +## For Skill Updates + +Validate the skill folder: + +```bash +python3 /Users/leofarias/.codex/skills/.system/skill-creator/scripts/quick_validate.py .agents/skills/superdeck-presentations +``` + +Also check for stale template markers: + +```bash +rg -n "TO[D]O|FIX[M]E|\\[TO[D]O" .agents/skills/superdeck-presentations +``` + +Confirm the trigger description mentions the concrete contexts that should load this skill: `slides.md`, SuperDeck Markdown, block layout, widgets, assets/images, `DeckOptions`, styles/templates, slide parts, CLI builds, and plugins. + +## Common Failure Checks + +Build cannot find slides: + +- Ensure `slides.md` is at the project root. +- Run `superdeck setup` if `.superdeck/` assets or macOS entitlements are missing. + +Invalid directive options: + +- Use strict YAML inside braces. +- Prefer multiline options for multiple keys. +- Quote strings with punctuation or YAML keywords. +- Check braces are balanced. + +Unexpected layout: + +- Remember: sections stack vertically; blocks inside a section are horizontal. +- Set `align` on blocks/widgets for visible alignment. +- Use block/widget `scrollable: true`; do not set `scrollable` on sections. +- Use `flex` integers; invalid values throw during parse/model validation. + +Widget does not render: + +- Verify the widget name is registered in `DeckOptions.widgets`. +- Built-ins are `image`, `dartpad`, and `qrcode`. +- Check that widget arguments do not collide with reserved block keys: `name`, `align`, `flex`, `scrollable`. +- Remember that reserved block keys are consumed by SuperDeck and are not passed to custom widget args. +- If using shorthand, verify the directive name exactly matches the registered widget name. +- If using Ack-generated args wrappers, confirm the schema is a top-level `@AckType()` declaration, the `part` file is present, generator dependencies are installed, and `build_runner` has regenerated the `.g.dart` file. +- Let the on-slide error guide factory parse/build failures. + +DartPad fails: + +- Confirm `id` is only the gist ID, not the full DartPad or GitHub URL. +- Confirm the gist contains `main.dart`. +- Test the snippet directly at `https://dartpad.dev/?id=`. +- Check WebView support on the target platform. +- Check network access to `dartpad.dev`; offline decks should not depend on `@dartpad`. +- For macOS, run `superdeck setup` so required network entitlements are patched. +- Remember the embedded WebView blocks navigation away from `dartpad.dev`; external-domain links inside DartPad will not navigate in the embedded view. +- If the WebView stays blank, check whether `onPageFinished` fires and whether the target platform's `webview_flutter` implementation is available. + +QR code fails: + +- Confirm the widget tag is `@qrcode`, not `@qr` or `@qrCode`. +- Confirm `text` is present, non-empty, and no more than 1000 characters. +- Use `size` from 1 to 1000; integers and doubles are accepted. +- Use `errorCorrection` values `low`, `medium`, `high`, `highest`, or aliases `l`, `m`, `q`, `h`. +- Use valid hex colors for `backgroundColor` and `foregroundColor`. +- Scan the QR code on the target display/projector; build success does not prove scan distance. + +Images fail: + +- Confirm which image path is being used: Markdown `![Alt](src)` inside content or widget-block `@image { src: ... }`. +- If the image should behave like normal slide content, use a standalone Markdown image. If it needs `fit`, fixed dimensions, column sizing, alignment, scrolling, or `data:` URI support, use `@image`. +- For Markdown images, reject `asset:` URLs and `..` traversal. +- For Markdown images, do not use `data:` URIs; use `@image` for `data:` sources. +- For web/release, declare project asset directories in `pubspec.yaml`. +- Prefer portable relative asset paths or URLs over absolute local paths for shared decks. + +Style/template errors: + +- Verify style names exist in the correct namespace. Template slides use `SlideTemplate.styles`; non-template slides use `DeckOptions.styles`. +- Use `template: none` only to opt out of `defaultTemplate`. +- Do not register a template named `none`; it is reserved. diff --git a/demo/.superdeck/build_status.json b/demo/.superdeck/build_status.json index 18c6e511..1dd003bf 100644 --- a/demo/.superdeck/build_status.json +++ b/demo/.superdeck/build_status.json @@ -1,6 +1,6 @@ { "status": "success", - "timestamp": "2026-06-16T18:16:23.856897Z", - "slideCount": 28, + "timestamp": "2026-07-08T17:07:26.733502Z", + "slideCount": 29, "error": null } \ No newline at end of file diff --git a/demo/.superdeck/superdeck.json b/demo/.superdeck/superdeck.json index 3cf84c78..af43596b 100644 --- a/demo/.superdeck/superdeck.json +++ b/demo/.superdeck/superdeck.json @@ -315,6 +315,61 @@ ], "comments": [] }, + { + "key": "DGz7HMmA", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": "### Ack-Generated Widget Args {.heading}\n", + "flex": 1, + "scrollable": false, + "type": "block" + } + ], + "flex": 1, + "type": "section" + }, + { + "blocks": [ + { + "content": "\n\nAck can validate custom widget arguments and generate typed getters over the same `Map` payload.\n", + "align": "center", + "flex": 1, + "scrollable": false, + "type": "block" + } + ], + "flex": 1, + "type": "section" + }, + { + "blocks": [ + { + "content": "\n\n```markdown\n@ack-metric-card {\n flex: 2\n align: center\n label: Activation\n value: \"72%\"\n caption: \"Parsed through Ack-generated typed getters\"\n tone: green\n}\n```\n", + "flex": 3, + "scrollable": false, + "type": "block" + }, + { + "name": "ack-metric-card", + "align": "center", + "flex": 2, + "scrollable": false, + "type": "widget", + "label": "Activation", + "value": "72%", + "caption": "Parsed through Ack-generated typed getters", + "tone": "green" + } + ], + "flex": 4, + "type": "section" + } + ], + "comments": [] + }, { "key": "C8r6gb6A", "options": {}, diff --git a/demo/.superdeck/superdeck_full.json b/demo/.superdeck/superdeck_full.json index 25f75fa2..2debc892 100644 --- a/demo/.superdeck/superdeck_full.json +++ b/demo/.superdeck/superdeck_full.json @@ -1031,6 +1031,136 @@ ], "comments": [] }, + { + "key": "DGz7HMmA", + "options": {}, + "sections": [ + { + "blocks": [ + { + "content": { + "type": "document", + "children": [ + { + "type": "element", + "tag": "h3", + "children": [ + { + "type": "text", + "text": "Ack-Generated Widget Args {.heading}" + } + ], + "generatedId": "ack-generated-widget-args-heading" + } + ], + "linkReferences": {}, + "footnoteLabels": [], + "footnoteReferences": {} + }, + "flex": 1, + "scrollable": false, + "type": "block" + } + ], + "flex": 1, + "type": "section" + }, + { + "blocks": [ + { + "content": { + "type": "document", + "children": [ + { + "type": "element", + "tag": "p", + "children": [ + { + "type": "text", + "text": "Ack can validate custom widget arguments and generate typed getters over the same " + }, + { + "type": "element", + "tag": "code", + "children": [ + { + "type": "text", + "text": "Map<String, Object?>" + } + ] + }, + { + "type": "text", + "text": " payload." + } + ] + } + ], + "linkReferences": {}, + "footnoteLabels": [], + "footnoteReferences": {} + }, + "align": "center", + "flex": 1, + "scrollable": false, + "type": "block" + } + ], + "flex": 1, + "type": "section" + }, + { + "blocks": [ + { + "content": { + "type": "document", + "children": [ + { + "type": "element", + "tag": "pre", + "children": [ + { + "type": "element", + "tag": "code", + "attributes": { + "class": "language-markdown" + }, + "children": [ + { + "type": "text", + "text": "@ack-metric-card {\n flex: 2\n align: center\n label: Activation\n value: "72%"\n caption: "Parsed through Ack-generated typed getters"\n tone: green\n}\n" + } + ] + } + ] + } + ], + "linkReferences": {}, + "footnoteLabels": [], + "footnoteReferences": {} + }, + "flex": 3, + "scrollable": false, + "type": "block" + }, + { + "name": "ack-metric-card", + "align": "center", + "flex": 2, + "scrollable": false, + "type": "widget", + "label": "Activation", + "value": "72%", + "caption": "Parsed through Ack-generated typed getters", + "tone": "green" + } + ], + "flex": 4, + "type": "section" + } + ], + "comments": [] + }, { "key": "C8r6gb6A", "options": {}, diff --git a/demo/lib/src/widgets/ack_metric_card.dart b/demo/lib/src/widgets/ack_metric_card.dart new file mode 100644 index 00000000..4a4bad16 --- /dev/null +++ b/demo/lib/src/widgets/ack_metric_card.dart @@ -0,0 +1,80 @@ +import 'package:ack/ack.dart'; +import 'package:ack_annotations/ack_annotations.dart'; +import 'package:flutter/material.dart'; + +part 'ack_metric_card.g.dart'; + +@AckType(name: 'AckMetricCardArgs') +final ackMetricCardArgsSchema = Ack.object({ + 'label': Ack.string().notEmpty(), + 'value': Ack.string().notEmpty(), + 'caption': Ack.string().optional(), + 'tone': Ack.enumString(['blue', 'green', 'orange', 'purple']).optional(), +}); + +class AckMetricCard extends StatelessWidget { + final AckMetricCardArgsType data; + + AckMetricCard(Map args, {super.key}) + : data = AckMetricCardArgsType.parse(args); + + Color _toneColor(String? tone) { + return switch (tone) { + 'green' => const Color(0xFF047857), + 'orange' => const Color(0xFFB45309), + 'purple' => const Color(0xFF7C3AED), + _ => const Color(0xFF2563EB), + }; + } + + @override + Widget build(BuildContext context) { + final color = _toneColor(data.tone); + + return Center( + child: Container( + width: 360, + padding: const EdgeInsets.all(28), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.12), + border: Border.all(color: color, width: 2), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + data.label.toUpperCase(), + style: TextStyle( + color: color, + fontSize: 14, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 12), + Text( + data.value, + style: const TextStyle( + color: Color(0xFF111827), + fontSize: 48, + fontWeight: FontWeight.w800, + ), + ), + if (data.caption != null) ...[ + const SizedBox(height: 10), + Text( + data.caption!, + style: const TextStyle( + color: Color(0xFF4B5563), + fontSize: 18, + height: 1.35, + ), + ), + ], + ], + ), + ), + ); + } +} diff --git a/demo/lib/src/widgets/ack_metric_card.g.dart b/demo/lib/src/widgets/ack_metric_card.g.dart new file mode 100644 index 00000000..772a58c9 --- /dev/null +++ b/demo/lib/src/widgets/ack_metric_card.g.dart @@ -0,0 +1,34 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// dart format width=80 + +// ************************************************************************** +// AckSchemaGenerator +// ************************************************************************** + +part of 'ack_metric_card.dart'; + +/// Extension type for AckMetricCardArgs +extension type AckMetricCardArgsType(Map _data) + implements Map { + static AckMetricCardArgsType parse(Object? data) { + return ackMetricCardArgsSchema.parseAs( + data, + (validated) => AckMetricCardArgsType(validated as Map), + ); + } + + static SchemaResult safeParse(Object? data) { + return ackMetricCardArgsSchema.safeParseAs( + data, + (validated) => AckMetricCardArgsType(validated as Map), + ); + } + + String get label => _data['label'] as String; + + String get value => _data['value'] as String; + + String? get caption => _data['caption'] as String?; + + String? get tone => _data['tone'] as String?; +} diff --git a/demo/lib/src/widgets/demo_widgets.dart b/demo/lib/src/widgets/demo_widgets.dart index bdf5aef1..a6b8cd53 100644 --- a/demo/lib/src/widgets/demo_widgets.dart +++ b/demo/lib/src/widgets/demo_widgets.dart @@ -6,6 +6,7 @@ import '../examples/mix/box_with_variants.dart' as mix_variants; import '../examples/mix/simple_box.dart' as mix_simple_box; import '../examples/select.dart' as naked_select; import '../examples/button.dart' as remix_button; +import 'ack_metric_card.dart'; /// Auto-registered demo widgets for Superdeck presentations. /// @@ -18,6 +19,11 @@ import '../examples/button.dart' as remix_button; /// @naked-select /// /// @remix-button +/// +/// @ack-metric-card { +/// label: Activation +/// value: "72%" +/// } /// ``` /// /// The QR code widget is now a built-in widget available as `@qrcode`. @@ -40,6 +46,7 @@ Map get demoWidgets => { 'remix-button': (_) => _DemoWrapper( child: Transform.scale(scale: 1.2, child: remix_button.ButtonExample()), ), + 'ack-metric-card': AckMetricCard.new, }; /// Wrapper widget that constrains demo widgets to their intrinsic size. diff --git a/demo/pubspec.yaml b/demo/pubspec.yaml index 77990526..5077117e 100644 --- a/demo/pubspec.yaml +++ b/demo/pubspec.yaml @@ -8,6 +8,8 @@ environment: dependencies: flutter: sdk: flutter + ack: 1.0.1 + ack_annotations: 1.0.1 google_fonts: ^8.1.0 mesh: ^0.4.3 mix: ^2.1.0 @@ -22,6 +24,8 @@ dev_dependencies: integration_test: sdk: flutter flutter_lints: ^5.0.0 + build_runner: ^2.5.4 + ack_generator: 1.0.1 superdeck_builder: ^1.0.0 superdeck_cli: ^1.0.0 superdeck_mermaid: ^1.0.0 diff --git a/demo/slides.md b/demo/slides.md index 21a024ff..b1e7fd8f 100644 --- a/demo/slides.md +++ b/demo/slides.md @@ -178,6 +178,46 @@ Embed interactive Flutter widgets directly in your slides! --- +### Ack-Generated Widget Args {.heading} + +@section { + flex: 1 +} +@block { + align: center +} + +Ack can validate custom widget arguments and generate typed getters over the same `Map` payload. + +@section { + flex: 4 +} +@block { + flex: 3 +} + +```markdown +@ack-metric-card { + flex: 2 + align: center + label: Activation + value: "72%" + caption: "Parsed through Ack-generated typed getters" + tone: green +} +``` + +@ack-metric-card { + flex: 2 + align: center + label: Activation + value: "72%" + caption: "Parsed through Ack-generated typed getters" + tone: green +} + +--- + ### Mix Box Example {.heading} @block { diff --git a/demo/test/ack_metric_card_test.dart b/demo/test/ack_metric_card_test.dart new file mode 100644 index 00000000..c404d8d0 --- /dev/null +++ b/demo/test/ack_metric_card_test.dart @@ -0,0 +1,30 @@ +import 'package:ack/ack.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:superdeck_example/src/widgets/ack_metric_card.dart'; + +void main() { + testWidgets('AckMetricCard renders generated typed args', (tester) async { + await tester.pumpWidget( + MaterialApp( + home: AckMetricCard({ + 'label': 'Activation', + 'value': '72%', + 'caption': 'Parsed through Ack-generated typed getters', + 'tone': 'green', + }), + ), + ); + + expect(find.text('ACTIVATION'), findsOneWidget); + expect(find.text('72%'), findsOneWidget); + expect( + find.text('Parsed through Ack-generated typed getters'), + findsOneWidget, + ); + }); + + test('AckMetricCard validates required args', () { + expect(() => AckMetricCard({'value': '72%'}), throwsA(isA())); + }); +} diff --git a/melos.yaml b/melos.yaml index 8a203a5d..95294380 100644 --- a/melos.yaml +++ b/melos.yaml @@ -14,7 +14,7 @@ command: dependencies: collection: ^1.18.0 mix: ^2.1.0 - ack: 1.0.0-beta.12 + ack: 1.0.1 # publish: # hooks: # pre: melos run gen:build diff --git a/packages/core/pubspec.yaml b/packages/core/pubspec.yaml index d286393c..d46bb251 100644 --- a/packages/core/pubspec.yaml +++ b/packages/core/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: yaml: ^3.1.2 collection: ^1.18.0 path: ^1.9.0 - ack: 1.0.0-beta.12 + ack: 1.0.1 dart_mappable: ^4.7.0 markdown: ^7.3.0 logging: ^1.3.0 diff --git a/packages/playground/pubspec.yaml b/packages/playground/pubspec.yaml index 41a63f8b..02b4d9b9 100644 --- a/packages/playground/pubspec.yaml +++ b/packages/playground/pubspec.yaml @@ -41,9 +41,9 @@ dependencies: gpt_markdown: ^1.1.4 dotprompt_dart: ^0.5.0 path: ^1.9.0 - ack: 1.0.0-beta.12 - ack_annotations: 1.0.0-beta.12 - ack_json_schema_builder: 1.0.0-beta.12 + ack: 1.0.1 + ack_annotations: 1.0.1 + ack_json_schema_builder: 1.0.1 dartantic_ai: ^3.1.0 json_schema_builder: ^0.1.5 @@ -54,7 +54,7 @@ dev_dependencies: sdk: flutter flutter_lints: ^5.0.0 build_runner: ^2.5.4 - ack_generator: 1.0.0-beta.12 + ack_generator: 1.0.1 flutter: uses-material-design: true From 9213b0623208b9a962af07f5524d5f4d0b8840a9 Mon Sep 17 00:00:00 2001 From: Leo Farias Date: Wed, 8 Jul 2026 19:47:49 -0400 Subject: [PATCH 3/4] docs: clarify hero class markers --- .agents/skills/superdeck-presentations/SKILL.md | 1 + .../superdeck-presentations/references/authoring.md | 13 +++++++++++-- docs/reference/markdown-syntax.mdx | 9 ++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.agents/skills/superdeck-presentations/SKILL.md b/.agents/skills/superdeck-presentations/SKILL.md index f5bbd68d..f0188c7d 100644 --- a/.agents/skills/superdeck-presentations/SKILL.md +++ b/.agents/skills/superdeck-presentations/SKILL.md @@ -31,6 +31,7 @@ Load only the reference needed for the task: - A slide contains vertical sections. Each `@section` starts a new vertical row; blocks inside that section are laid out horizontally. - `@block` renders Markdown. `@widget` and any unrecognized `@name` render a `WidgetBlock`. - Images have two authoring paths. Prefer standalone Markdown `![alt](src)` when the image belongs in the Markdown content flow; use `@image { src: ... }` when the image needs block-level layout control such as `fit`, fixed size, `flex`, `align`, `scrollable`, or `data:` source support. +- Markdown class markers such as `{.heading}` or `{.title}` drive Hero transitions for supported Markdown elements; the class does not need a `hero-` prefix. Use the same tag on matching elements across adjacent slides, and do not duplicate the same tag on one slide. - `@column` is intentionally unsupported; use `@block`. - Block and widget `align` controls visible content alignment. Current renderer stores `section.align` but does not apply it to child layout; set `align` on child blocks/widgets when visual placement matters. - `scrollable` is valid on `@block` and widget blocks, not on `@section`. diff --git a/.agents/skills/superdeck-presentations/references/authoring.md b/.agents/skills/superdeck-presentations/references/authoring.md index 12dfa4e9..595cbc3a 100644 --- a/.agents/skills/superdeck-presentations/references/authoring.md +++ b/.agents/skills/superdeck-presentations/references/authoring.md @@ -358,9 +358,12 @@ SuperDeck uses GitHub-flavored Markdown plus custom builders: Supported alert labels include `NOTE`, `TIP`, `IMPORTANT`, `WARNING`, and `CAUTION`. -Hero markers can be attached to headings, images, and fenced code blocks: +Hero transition tags can be attached to headings, images, and fenced code blocks with Markdown class-marker syntax: ````markdown +# Overview {.heading} +## What changes next {.subheading} + # Roadmap {.hero-title} ![Diagram](assets/roadmap.png) {.hero-visual} @@ -369,7 +372,13 @@ void main() {} ``` ```` -The first valid class name is used as the hero tag. Do not rely on classes beginning with `--`; they are rejected. +The first valid class name is used as the Hero tag. The class does not need a `hero-` prefix: `{.heading}`, `{.subheading}`, `{.title}`, and `{.hero-title}` are all valid tag names when they match the identifier rules. + +Use the same tag on the element that should animate from one slide to the next. For example, a heading marked `{.heading}` on slide 1 will transition to the heading marked `{.heading}` on slide 2. + +Do not duplicate the same Hero tag on one slide. Flutter Hero transitions require one source/destination element for each tag in a route; if a slide contains two elements marked `{.heading}`, the transition is ambiguous and can fail. Use distinct tags such as `{.heading}` and `{.subheading}` for multiple animated text elements on the same slide. + +Do not rely on classes beginning with `--`; they are rejected. The class marker is stripped from rendered content and is not a Mix style selector. ## Authoring Patterns diff --git a/docs/reference/markdown-syntax.mdx b/docs/reference/markdown-syntax.mdx index 6f7a8015..e356467e 100644 --- a/docs/reference/markdown-syntax.mdx +++ b/docs/reference/markdown-syntax.mdx @@ -135,9 +135,16 @@ Use GitHub-style alert syntax: ### Hero classes -Use classes to animate matching elements between slides: +Use Markdown class markers to animate matching elements between slides. The first valid class name becomes the Flutter Hero tag, so it does not need to start with `hero-`. ````markdown +# Overview {.heading} +## What changes next {.subheading} + # Roadmap {.hero-title} ![Diagram](assets/roadmap.png) {.hero-visual} ```` + +Use the same tag for the element that should transition from one slide to the next. For example, one `{.heading}` on slide A can animate to one `{.heading}` on slide B. + +Do not use the same tag twice on one slide. Each Hero tag must identify one element per slide, so use separate tags like `{.heading}` and `{.subheading}` when multiple elements should animate. From 786739ca9d35ac9a0de95aef64f4dc41e839189f Mon Sep 17 00:00:00 2001 From: Leo Farias Date: Fri, 10 Jul 2026 11:15:21 -0400 Subject: [PATCH 4/4] docs: update SuperDeck skill for recent features --- .../skills/superdeck-presentations/SKILL.md | 9 ++-- .../references/authoring.md | 34 +++++++++++-- .../references/runtime-customization.md | 49 ++++++++++++++----- .../references/verification.md | 11 ++++- 4 files changed, 85 insertions(+), 18 deletions(-) diff --git a/.agents/skills/superdeck-presentations/SKILL.md b/.agents/skills/superdeck-presentations/SKILL.md index f0188c7d..6e5c6261 100644 --- a/.agents/skills/superdeck-presentations/SKILL.md +++ b/.agents/skills/superdeck-presentations/SKILL.md @@ -1,6 +1,6 @@ --- name: superdeck-presentations -description: Create, review, or edit SuperDeck presentations and presentation apps. Use when working with `slides.md`, SuperDeck Markdown syntax, `@section`/`@block`/`@widget` layout, built-in widgets such as `@image`, `@dartpad`, and `@qrcode`, custom Flutter widgets, `DeckOptions`, slide parts, templates, styles, images/assets, CLI builds, plugins, or validation of SuperDeck deck behavior. +description: Create, review, or edit SuperDeck presentations and presentation apps. Use when working with `slides.md`, SuperDeck Markdown syntax, fullscreen slides, `@section`/`@block`/`@widget` layout, built-in widgets such as `@image`, `@dartpad`, `@webview`, and `@qrcode`, custom Flutter widgets, `DeckOptions`, `BlockVariant`, slide parts, templates, styles, images/assets, CLI builds, plugins, or validation of SuperDeck deck behavior. --- # SuperDeck Presentations @@ -35,9 +35,11 @@ Load only the reference needed for the task: - `@column` is intentionally unsupported; use `@block`. - Block and widget `align` controls visible content alignment. Current renderer stores `section.align` but does not apply it to child layout; set `align` on child blocks/widgets when visual placement matters. - `scrollable` is valid on `@block` and widget blocks, not on `@section`. -- Built-ins `image`, `dartpad`, and `qrcode` are always registered and can be overridden by user widgets with the same name. -- `@dartpad` is the built-in WebView-backed widget; there is no separate built-in `@webview` authoring tag. +- `layout: fullscreen` removes resolved header/footer chrome while retaining the slide's resolved background and style. `normal` is the default. +- Built-ins `image`, `dartpad`, `webview`, and `qrcode` are always registered and can be overridden by user widgets with the same name. +- `@dartpad` and `@webview` use the same deck-scoped WebView controller cache. A `cacheKey` enables sequential reuse across remounts, never concurrent sharing by two live blocks. - Custom widgets must be registered in `DeckOptions.widgets`; use shorthand `@widgetName { ... }` in `slides.md` for registered widget names. +- `BlockVariant('name')` is a Dart/Mix stylesheet selector for all `WidgetBlock`s with that exact, case-sensitive name. It affects the matching container and its widget subtree, not `@block` content. - Styles, templates, widgets, slide parts, and plugins are configured in Dart through `DeckOptions`/`SuperDeckApp`, not through a separate `styles.yaml`. - The CLI reads `slides.md`, writes `.superdeck/superdeck.json`, and ensures `.superdeck/` is listed in Flutter assets unless `--skip-pubspec` is used. @@ -57,6 +59,7 @@ Use these files to resolve disputes or update this skill: - `packages/core/lib/src/deck/block_model.dart` - `packages/superdeck/lib/src/rendering/slides/slide_view.dart` - `packages/superdeck/lib/src/rendering/blocks/block_widget.dart` +- `packages/superdeck/lib/src/styling/block_variant.dart` - `packages/superdeck/lib/src/builtins/` - `packages/superdeck/lib/src/ui/widgets/webview_wrapper.dart` - `packages/playground/lib/features/ai/quick_agent/core/engine/schemas/deck_schemas.dart` diff --git a/.agents/skills/superdeck-presentations/references/authoring.md b/.agents/skills/superdeck-presentations/references/authoring.md index 595cbc3a..81063121 100644 --- a/.agents/skills/superdeck-presentations/references/authoring.md +++ b/.agents/skills/superdeck-presentations/references/authoring.md @@ -31,8 +31,9 @@ Supported frontmatter keys: - `title`: Used by navigation/export/chrome. - `style`: Named style from `DeckOptions.styles`, or from the active template's `styles`. +- `layout`: `normal` (default) or `fullscreen`. Fullscreen removes resolved header/footer chrome while retaining the resolved background and style. - `template`: Named `DeckOptions.templates` entry. Use `template: none` to opt out of `defaultTemplate`. -- Any other key: Preserved in `SlideOptions.args`. +- Any other key: Preserved in `SlideOptions.args`; the official keys above are not included there. Plain slides without frontmatter are valid: @@ -207,8 +208,9 @@ Arguments: - `theme`: `light` or `dark`. - `embed`: boolean, default `true`. - `run`: boolean, default `true`. +- `cacheKey`: optional key for sequential controller reuse across remounts. -DartPad renders through SuperDeck's internal `WebViewWrapper`; there is no separate built-in `@webview` tag. Verify the target platform supports WebViews and can reach `https://dartpad.dev`. +DartPad renders through SuperDeck's internal `WebViewWrapper`. Verify the target platform supports WebViews and can reach `https://dartpad.dev`. Share a DartPad snippet through a GitHub Gist: @@ -235,7 +237,31 @@ Runtime behavior to remember: - The wrapper blocks navigation away from the original host, so links to other domains will not navigate inside the embedded view. - The WebView is hidden until the page finishes loading, then fades in after a short delay. - The wrapper overlays refresh and clear-editor controls. -- State is kept alive while the slide tree keeps the widget alive; changing the DartPad URL reloads the WebView. +- A controller is cached per block by default, or by `cacheKey` when supplied, so returning to a slide does not reload the same URL. A key cannot be shared by two live widgets at once. +- Changing the DartPad URL reloads the WebView. + +### WebView + +Use `@webview` to embed a persistent `http` or `https` page. WebView blocks are edge-to-edge by default; use a `BlockVariant` rule in Dart only when a different container treatment is needed. + +```markdown +@webview { + url: "https://example.com" + title: "Example" + showControls: true +} +``` + +Arguments: + +- `url` required: absolute `http` or `https` URL. +- `cacheKey`: optional key for sequential controller reuse across remounts. +- `title`: label shown for static/thumbnail capture. +- `allowedHosts`: optional navigation allowlist; defaults to the source host. +- `showControls`: show a refresh control, default `false`. +- `javascript`: enable JavaScript, default `true`. + +Native implementations enforce `allowedHosts` through the navigation delegate. Flutter web's iframe implementation has no navigation callbacks, so this restriction cannot be enforced there. Static rendering shows the title or a placeholder instead of starting a live WebView. ### QR Code @@ -310,6 +336,8 @@ Or explicit form: All properties become the widget factory's `Map` arguments. Block-level controls such as `flex`, `align`, and `scrollable` are consumed by SuperDeck and are not passed as custom widget args. +To style every custom block with the same name, declare `BlockVariant('metricCard')` in a Dart `SlideStyle`. The match is exact and case-sensitive, and it applies to the widget block container plus descendants rather than to individual Markdown block instances. + Custom widget authoring rules: - Register the widget name in `DeckOptions.widgets` before using it. diff --git a/.agents/skills/superdeck-presentations/references/runtime-customization.md b/.agents/skills/superdeck-presentations/references/runtime-customization.md index d8d9ffe3..bd71ca25 100644 --- a/.agents/skills/superdeck-presentations/references/runtime-customization.md +++ b/.agents/skills/superdeck-presentations/references/runtime-customization.md @@ -113,7 +113,7 @@ Use Flutter's `LayoutBuilder` for sizing. SuperDeck does not export a public `Me If a widget factory is missing, SuperDeck renders `Widget not found: `. If the factory throws, SuperDeck renders error details on the slide. -Built-ins `image`, `dartpad`, and `qrcode` are registered first. User widgets with the same name override a built-in only when that name is used by the slide. +Built-ins `image`, `dartpad`, `webview`, and `qrcode` are registered first. User widgets with the same name override a built-in only when that name is used by the slide. ### Recommended Custom Widget Pattern @@ -254,7 +254,7 @@ Notes: - `align`, `flex`, `scrollable`, and `name` are reserved block keys; do not expect them inside `args`. - Widget names can include letters, digits, underscores, and hyphens because directive tags match `@[\w-]+`. -- A custom widget can override a built-in by registering `image`, `dartpad`, or `qrcode`, but do that only intentionally. +- A custom widget can override a built-in by registering `image`, `dartpad`, `webview`, or `qrcode`, but do that only intentionally. - Use `SlideConfiguration.of(context)` for slide metadata and `LayoutBuilder` for block size. ## Slide Parts @@ -285,6 +285,8 @@ class DeckHeader extends StatelessWidget implements PreferredSizeWidget { Header and footer consume vertical space from the 1280x720 slide content area. Background fills the full slide. +Set `layout: fullscreen` in a slide's front matter to remove the resolved header and footer for that slide. This affects deck parts, named-template parts, and `defaultTemplate` parts, but preserves the resolved background and style. + ## Styles and Templates Slide frontmatter selects styles/templates: @@ -307,6 +309,32 @@ Unknown templates or styles throw `ArgumentError` during configuration build, so Template styles are isolated. A slide using `template: brand` and `style: cover` looks up `cover` in `SlideTemplate.styles`, not `DeckOptions.styles`. +### Named Widget Block Styles + +Use `BlockVariant` to target every widget block with an exact, case-sensitive name. The selector resolves around the matching block container and its descendants, so container padding/margin and widget subtree styles see the same active variant. + +```dart +import 'package:mix/mix.dart'; +import 'package:superdeck/superdeck.dart'; + +const webviewBlock = BlockVariant('webview'); + +final options = DeckOptions( + baseStyle: SlideStyle( + blockContainer: BoxStyler( + padding: EdgeInsetsGeometryMix.all(40), + ).variants([ + VariantStyle( + webviewBlock, + BoxStyler(padding: EdgeInsetsGeometryMix.all(0)), + ), + ]), + ), +); +``` + +`BlockVariant('webview')` matches `@webview` and `@widget { name: "webview" }`; `BlockVariant('chart')` matches `@chart`. It does not select Markdown `@block` content, arbitrary `NamedVariant` values, or individual instances. SuperDeck already applies a zero-padding, zero-margin `BlockVariant('webview')` rule by default, so WebView blocks are edge-to-edge unless the style overrides it. + ## Images and Assets The CLI manages `.superdeck/` assets and ensures this entry exists unless `--skip-pubspec` is used: @@ -390,27 +418,26 @@ To create a shareable DartPad for a deck: Use `run: false` for exercises where the audience should edit before running. -## DartPad WebView Runtime +## WebView Runtime -`@dartpad` renders through `WebViewWrapper`; it is not a generic web embed API. Do not document or use `@webview` unless the app registers its own custom widget. +`@dartpad` and `@webview` render through `WebViewWrapper`. Live controllers are cached at deck scope by the block runtime key or an explicit `cacheKey`; reuse is sequential, not concurrent. Static rendering produces a placeholder instead of creating a controller. Behavior: - Creates a `webview_flutter` `WebViewController`. - Enables `JavaScriptMode.unrestricted` because DartPad requires JavaScript. -- Loads the generated DartPad URL from `DartPadDto.toUrl()`. -- Allows navigation only when the destination host matches the original DartPad URL host; other hosts are blocked. +- Loads the generated DartPad URL from `DartPadDto.toUrl()` or the validated `@webview` URL. +- Allows navigation only to the source host by default; `@webview.allowedHosts` can extend the list. Flutter web cannot enforce this policy because its iframe implementation does not expose navigation callbacks. - Hides the WebView initially, then fades it in after `onPageFinished` plus a 500 ms delay. -- Keeps the state alive with `AutomaticKeepAliveClientMixin`. -- Reloads when the URL changes. +- Reuses a cached controller when remounting the same key and URL, and reloads when the URL changes. - Displays overlay icon buttons for refresh and clearing the CodeMirror editor. Platform notes: - The package depends on `webview_flutter` and `webview_flutter_web`. -- `superdeck setup` patches macOS network entitlements when a macOS runner exists; run it for desktop decks using DartPad. -- Embedded DartPad needs network access to `dartpad.dev`; offline presentations should avoid relying on `@dartpad`. -- Because navigation is host-restricted, use the surrounding slide content for external links instead of expecting links inside DartPad to open other domains. +- `superdeck setup` patches macOS network entitlements when a macOS runner exists; run it for desktop decks using DartPad or WebView. +- Embedded DartPad needs network access to `dartpad.dev`; embedded pages need network access to their configured URL. +- Because navigation is host-restricted on native targets, use surrounding slide content for external links instead of expecting WebView navigation to arbitrary domains. ## Deployment Notes diff --git a/.agents/skills/superdeck-presentations/references/verification.md b/.agents/skills/superdeck-presentations/references/verification.md index 5ac7dadf..4755b6f5 100644 --- a/.agents/skills/superdeck-presentations/references/verification.md +++ b/.agents/skills/superdeck-presentations/references/verification.md @@ -100,11 +100,12 @@ Unexpected layout: - Set `align` on blocks/widgets for visible alignment. - Use block/widget `scrollable: true`; do not set `scrollable` on sections. - Use `flex` integers; invalid values throw during parse/model validation. +- For `layout: fullscreen`, verify that header/footer are absent while the intended background and style remain. Widget does not render: - Verify the widget name is registered in `DeckOptions.widgets`. -- Built-ins are `image`, `dartpad`, and `qrcode`. +- Built-ins are `image`, `dartpad`, `webview`, and `qrcode`. - Check that widget arguments do not collide with reserved block keys: `name`, `align`, `flex`, `scrollable`. - Remember that reserved block keys are consumed by SuperDeck and are not passed to custom widget args. - If using shorthand, verify the directive name exactly matches the registered widget name. @@ -122,6 +123,13 @@ DartPad fails: - Remember the embedded WebView blocks navigation away from `dartpad.dev`; external-domain links inside DartPad will not navigate in the embedded view. - If the WebView stays blank, check whether `onPageFinished` fires and whether the target platform's `webview_flutter` implementation is available. +WebView fails: + +- Confirm `url` is an absolute `http` or `https` URL. +- Check network access to the source page and target platform WebView support. +- Use `cacheKey` only for sequential reuse; two live blocks with the same key receive separate local controllers. +- On Flutter web, do not rely on `allowedHosts`, JavaScript injection, or page-finished callbacks: the iframe implementation does not support those controller APIs. + QR code fails: - Confirm the widget tag is `@qrcode`, not `@qr` or `@qrCode`. @@ -145,3 +153,4 @@ Style/template errors: - Verify style names exist in the correct namespace. Template slides use `SlideTemplate.styles`; non-template slides use `DeckOptions.styles`. - Use `template: none` only to opt out of `defaultTemplate`. - Do not register a template named `none`; it is reserved. +- `BlockVariant('name')` matches only widget blocks with that exact, case-sensitive name; it does not select Markdown `@block` content.