diff --git a/.spec/decisions/live_ui/live_ui.tooling.focused_example_alignment.md b/.spec/decisions/live_ui/live_ui.tooling.focused_example_alignment.md
new file mode 100644
index 00000000..aef77b3b
--- /dev/null
+++ b/.spec/decisions/live_ui/live_ui.tooling.focused_example_alignment.md
@@ -0,0 +1,85 @@
+---
+id: live_ui.tooling.focused_example_alignment
+status: accepted
+date: 2026-04-26
+affects:
+ - live_ui.package
+ - live_ui.tooling
+---
+
+# Focused Example Alignment for LiveUi
+
+## Context
+
+The repository now treats the root `examples/` directory as the authoritative
+widget-focused example suite. Each example is a standalone application with one
+clear widget or construct focus, one repeatable review workflow, and one shared
+runtime-target story across `live_ui`, `desktop_ui`, `elm_ui`, and
+`terminal_ui`.
+
+`live_ui` still carries a different maintainer surface:
+
+- a package-local demo workbench
+- a small package-owned maintained example catalog organized as native,
+ canonical, and mixed lanes
+- a widget preview inventory whose ids and coverage do not match the
+ repository example inventory one-for-one
+
+That creates three kinds of drift:
+
+1. **Inventory Drift** - maintainers have to reason about a repository example
+ inventory and a separate `live_ui` package inventory with different ids and
+ coverage rules.
+2. **Workflow Drift** - the root suite has moved toward focused standalone app
+ review, while `live_ui` still centers a package-local demo/workbench and
+ comparison-oriented package examples.
+3. **Meaning Drift** - package examples can become abstractions about
+ categories, continuity lanes, or demo states instead of concrete widget
+ examples that match what the ecosystem already exposes publicly.
+
+The package still needs strong maintainer review for native rendering,
+canonical rendering, transport behavior, and runtime authority. The question is
+what example surface should carry that burden.
+
+## Decision
+
+`live_ui` shall retire its current package-local demo/workbench and divergent
+package-owned example catalog in favor of the same focused example inventory
+used by the repository example suite, specialized for direct native `live_ui`
+widgets and runtime entrypoints.
+
+1. **One Focused Inventory** - The package shall align its maintained example
+ identities one-for-one with the repository widget-focused example inventory
+ instead of keeping a second package-specific catalog with unrelated names.
+2. **Native Package Specialization** - Each aligned `live_ui` example shall use
+ the package's own native widgets and runtime entrypoints directly rather than
+ re-authoring the package example surface through `unified_ui`.
+3. **No Aggregate Demo Workbench** - The package shall not keep a package-local
+ demo/workbench as the primary review entry point once the aligned focused
+ examples exist.
+4. **Canonical Review on the Same Ids** - When package validation still needs
+ canonical `UnifiedIUR` rendering, transport inspection, or continuity
+ comparison, those workflows shall attach to the same focused example ids
+ rather than introducing separate example families or demo-only artifacts.
+5. **Package Tooling Follows the Inventory** - `mix live_ui.preview`,
+ `mix live_ui.inspect`, `mix live_ui.export`, and `mix live_ui.validate`
+ shall operate on the aligned focused example inventory and its package
+ specializations.
+6. **Repository Examples Stay Authoritative** - The repository `examples/`
+ inventory remains the authoritative cross-runtime example catalog; `live_ui`
+ specializes that inventory for native runtime review rather than replacing
+ or forking it.
+
+## Consequences
+
+- Maintainers review the same widget-focused example identities across the root
+ suite and the `live_ui` package.
+- The package loses a separate demo/workbench concept and the extra cognitive
+ layer that goes with it.
+- Example coverage gaps become easier to spot because missing package examples
+ map directly to missing repository example ids.
+- Native `live_ui` review becomes more concrete because examples are anchored to
+ real widget-focused application stories instead of category-level demo lanes.
+- Canonical rendering and transport diagnostics still remain available, but
+ they become inspection modes on the same example ids rather than a separate
+ maintained example taxonomy.
diff --git a/.spec/planning/live_ui/README.md b/.spec/planning/live_ui/README.md
index 9a79d70c..2c3b033a 100644
--- a/.spec/planning/live_ui/README.md
+++ b/.spec/planning/live_ui/README.md
@@ -40,6 +40,7 @@ The plan aligns to:
15. [Phase 15 - Canonical Renderer Convergence](./phase-15-canonical-renderer-convergence.md): finish the convergence work so canonical `UnifiedIUR` rendering targets the same widget component boundaries used by direct native screens.
16. [Phase 16 - Advanced Widget Integration Tests](./phase-16-advanced-widget-integration-tests.md): validate the complete widget-component architecture end to end across all widget families.
17. [Phase 17 - Tooling, Demo, Validation, and Release Readiness for Widget Components](./phase-17-tooling-demo-validation-and-release-readiness-for-widget-components.md): finish the widget-component transition with demo/example upgrades, tooling visibility, validation gates, documentation, and cleanup of legacy helper-only paths.
+18. [Phase 18 - Focused Example Alignment and Demo Retirement](./phase-18-focused-example-alignment-and-demo-retirement.md): replace the package-local demo and divergent example catalog with `live_ui`-native specializations of the repository widget-focused example inventory, and move tooling, docs, and validation onto those aligned example ids.
## Shared Conventions
- Numbering:
diff --git a/.spec/planning/live_ui/phase-18-focused-example-alignment-and-demo-retirement.md b/.spec/planning/live_ui/phase-18-focused-example-alignment-and-demo-retirement.md
new file mode 100644
index 00000000..40be809e
--- /dev/null
+++ b/.spec/planning/live_ui/phase-18-focused-example-alignment-and-demo-retirement.md
@@ -0,0 +1,95 @@
+# Phase 18 - Focused Example Alignment and Demo Retirement
+
+Back to index: [README](./README.md)
+
+## Relevant Shared APIs / Interfaces
+- `LiveUi.Tooling`
+- `LiveUi.Examples`
+- `LiveUi.Demo`
+- `LiveUi.Info`
+- `LiveUi.Reference`
+- `LiveUi.Renderer`
+- `LiveUi.Runtime`
+- `LiveUi.Component`
+- `UnifiedExamples.*`
+- `mix live_ui.preview`
+- `mix live_ui.inspect`
+- `mix live_ui.export`
+- `mix live_ui.validate`
+
+## Relevant Assumptions / Defaults
+- The repository `examples/` inventory is now the authoritative widget-focused example catalog for the ecosystem.
+- `live_ui` should specialize that same inventory for direct native runtime review instead of maintaining a second demo/workbench or divergent example taxonomy.
+- Native `live_ui` widget usage remains the primary package-specialized example path, while canonical `UnifiedIUR` review, transport inspection, and continuity checks remain available as maintainer tooling on the same example ids.
+- This phase is expected to retire the current package-local demo/workbench and the small native/canonical/mixed package-owned example catalog once aligned focused examples replace them.
+
+[ ] 18 Phase 18 - Focused Example Alignment and Demo Retirement
+ Replace the current package-local demo and divergent example catalog with the same focused widget example inventory used by the repository suite, specialized for direct native `live_ui` widgets and runtime entrypoints while keeping canonical review and transport tooling attached to those same example ids.
+
+ [ ] 18.1 Section - Focused Example Inventory Alignment
+ Align the package example inventory one-for-one with the authoritative repository widget example catalog.
+
+ [ ] 18.1.1 Task - Define the aligned `live_ui` example identity and coverage model
+ Establish the package-owned example ids, naming, metadata, and coverage rules so every repository example has one matching `live_ui` specialization.
+
+ [ ] 18.1.1.1 Subtask - Create a one-for-one mapping between the root `examples/` inventory and the package-specialized `live_ui` example ids, preserving widget identity and reviewer intent.
+ [ ] 18.1.1.2 Subtask - Define package metadata for each aligned example, including native runtime entrypoint, canonical review obligations, transport coverage, and continuity expectations.
+ [ ] 18.1.1.3 Subtask - Add tests that fail when a repository example id is missing from the `live_ui` package-specialized inventory or when a retired package-only id remains exposed.
+
+ [ ] 18.1.2 Task - Implement the package-local structure for aligned focused examples
+ Replace the current divergent example surface with a structure that is clearly organized around the same widget-focused examples as the root suite.
+
+ [ ] 18.1.2.1 Subtask - Create or reorganize the package example modules so maintainers can locate each `live_ui` specialization by the same widget-focused identity used in the repository suite.
+ [ ] 18.1.2.2 Subtask - Ensure each aligned example uses direct native `live_ui` widgets and runtime entrypoints rather than re-authoring the example through `unified_ui`.
+ [ ] 18.1.2.3 Subtask - Add tests that prove the aligned examples remain package-local `live_ui` specializations rather than thin wrappers around the root example applications.
+
+ [ ] 18.2 Section - Tooling and Review Workflow Realignment
+ Move maintainer tooling from the old demo/catalog model onto the aligned focused example inventory.
+
+ [ ] 18.2.1 Task - Retarget preview, inspection, export, and validation workflows to aligned example ids
+ Make the package tooling operate on the same focused example identities that the repository suite exposes publicly.
+
+ [ ] 18.2.1.1 Subtask - Update `mix live_ui.preview`, `mix live_ui.inspect`, and `mix live_ui.export` so they resolve aligned focused example ids instead of the current native/canonical/mixed package catalog.
+ [ ] 18.2.1.2 Subtask - Keep canonical rendering, transport diagnostics, and continuity review available as inspection modes on the aligned example ids rather than as separate example families.
+ [ ] 18.2.1.3 Subtask - Add tests that prove tooling output stays deterministic when maintainers review native runtime behavior and canonical behavior for the same aligned example id.
+
+ [ ] 18.2.2 Task - Remove the package-local demo/workbench and divergent example taxonomy
+ Retire the surfaces that no longer match the package and tooling specs.
+
+ [ ] 18.2.2.1 Subtask - Remove `LiveUi.Demo` and its package-local workbench routing, catalog, and widget preview entrypoints once aligned focused examples replace them.
+ [ ] 18.2.2.2 Subtask - Remove or migrate the current native/canonical/mixed package-owned example catalog so no divergent package-only example names remain public.
+ [ ] 18.2.2.3 Subtask - Add tests that fail if demo-only commands, divergent review artifacts, or package-only example ids are accidentally reintroduced.
+
+ [ ] 18.3 Section - Documentation, Validation, and Release Policy Alignment
+ Make the aligned focused example model the official package story in docs, validation, and release-readiness workflows.
+
+ [ ] 18.3.1 Task - Update package documentation and maintainer guidance
+ Replace the old demo-and-catalog narrative with the new aligned focused example story.
+
+ [ ] 18.3.1.1 Subtask - Update `packages/live_ui/README.md` and the package guides to describe the aligned focused example inventory as the maintainer entry point.
+ [ ] 18.3.1.2 Subtask - Document how native runtime review, canonical review, transport inspection, and continuity checks attach to the same aligned example ids.
+ [ ] 18.3.1.3 Subtask - Add documentation tests that fail if package docs mention the retired demo/workbench or divergent example taxonomy.
+
+ [ ] 18.3.2 Task - Tighten validation and traceability around aligned examples
+ Make the new example contract enforceable and visible in release-readiness workflows.
+
+ [ ] 18.3.2.1 Subtask - Extend `mix live_ui.validate` so it verifies one-for-one coverage against the repository example inventory and fails when package examples drift or disappear.
+ [ ] 18.3.2.2 Subtask - Update the live_ui planning traceability manifest so the new package and tooling requirements point at this phase explicitly.
+ [ ] 18.3.2.3 Subtask - Add release-readiness checks that fail when tooling, docs, and validation disagree about the aligned example inventory or the removal of the package-local demo.
+
+ [ ] 18.4 Section - Phase 18 Integration Tests
+ Validate the aligned example inventory, tooling migration, and demo retirement end to end.
+
+ [ ] 18.4.1 Task - Focused example alignment integration scenarios
+ Verify the package now exposes the same focused example identities as the repository suite for `live_ui`-native review.
+
+ [ ] 18.4.1.1 Subtask - Verify every repository widget-focused example id has one matching `live_ui` package specialization.
+ [ ] 18.4.1.2 Subtask - Verify native runtime review and canonical inspection operate on the same aligned example ids without introducing package-only aliases.
+ [ ] 18.4.1.3 Subtask - Verify package examples remain direct native `live_ui` widget/runtime specializations instead of reusing the authored `unified_ui` example implementations.
+
+ [ ] 18.4.2 Task - Demo retirement and release-readiness integration scenarios
+ Verify the old demo/workbench surface is fully retired and the new example model is the only supported maintainer path.
+
+ [ ] 18.4.2.1 Subtask - Verify `LiveUi.Demo`, demo-only routes, and divergent example commands are gone or fail with explicit migration guidance.
+ [ ] 18.4.2.2 Subtask - Verify package validation and release-readiness gates fail when aligned example coverage drifts from the repository example inventory.
+ [ ] 18.4.2.3 Subtask - Verify package docs, tooling output, and traceability all describe the same focused example alignment story.
diff --git a/.spec/planning/live_ui/spec-traceability.json b/.spec/planning/live_ui/spec-traceability.json
index 937cbcf0..2f1f3ba9 100644
--- a/.spec/planning/live_ui/spec-traceability.json
+++ b/.spec/planning/live_ui/spec-traceability.json
@@ -375,6 +375,20 @@
],
"ownership_note": "Phase 11: Native surface is a mountable LiveComponent-oriented widget library."
},
+ {
+ "requirement_id": "live_ui.package.focused_example_specialization",
+ "scope": "direct",
+ "source_file": ".spec/specs/live_ui/package.spec.md",
+ "primary_plan_refs": [
+ "18.1.1"
+ ],
+ "supporting_plan_refs": [
+ "18.1.2",
+ "18.2.1",
+ "18.3.2"
+ ],
+ "ownership_note": "Phase 18: Package examples align one-for-one with the repository focused example inventory and specialize it for native live_ui review."
+ },
{
"requirement_id": "live_ui.structure.mix_library_layout",
"scope": "direct",
@@ -851,28 +865,29 @@
"scope": "direct",
"source_file": ".spec/specs/live_ui/tooling.spec.md",
"primary_plan_refs": [
- "6.1.1"
+ "18.1.1"
],
"supporting_plan_refs": [
- "2.4.1",
- "4.4.1",
- "5.4.1"
+ "18.1.2",
+ "18.2.1",
+ "18.4.1"
],
- "ownership_note": "Direct tooling obligation."
+ "ownership_note": "Phase 18: Maintained reference examples align one-for-one with the repository focused example inventory."
},
{
"requirement_id": "live_ui.tooling.preview_and_inspection",
"scope": "direct",
"source_file": ".spec/specs/live_ui/tooling.spec.md",
"primary_plan_refs": [
- "6.2.1"
+ "18.2.1"
],
"supporting_plan_refs": [
"5.3.1",
"5.3.2",
- "6.3.2"
+ "6.3.2",
+ "18.2.2"
],
- "ownership_note": "Direct tooling obligation."
+ "ownership_note": "Phase 18: Preview and inspection workflows attach native, canonical, and transport review to the aligned focused example ids."
},
{
"requirement_id": "live_ui.tooling.validation_workflow",
@@ -884,7 +899,9 @@
"supporting_plan_refs": [
"4.3.1",
"5.5.1",
- "6.5.2"
+ "6.5.2",
+ "18.3.2",
+ "18.4.2"
],
"ownership_note": "Direct tooling obligation."
},
@@ -897,10 +914,26 @@
],
"supporting_plan_refs": [
"6.4.2",
- "6.5.2"
+ "6.5.2",
+ "18.3.1",
+ "18.4.2"
],
"ownership_note": "Direct tooling obligation."
},
+ {
+ "requirement_id": "live_ui.tooling.no_package_local_demo_workbench",
+ "scope": "direct",
+ "source_file": ".spec/specs/live_ui/tooling.spec.md",
+ "primary_plan_refs": [
+ "18.2.2"
+ ],
+ "supporting_plan_refs": [
+ "18.3.1",
+ "18.3.2",
+ "18.4.2"
+ ],
+ "ownership_note": "Phase 18: Retire the package-local demo/workbench and divergent widget catalog in favor of aligned focused examples."
+ },
{
"requirement_id": "unified_iur.widgets.input_and_navigation",
"scope": "upstream_constraint",
diff --git a/.spec/planning/live_ui/spec-traceability.md b/.spec/planning/live_ui/spec-traceability.md
index 6347999f..a733c95d 100644
--- a/.spec/planning/live_ui/spec-traceability.md
+++ b/.spec/planning/live_ui/spec-traceability.md
@@ -58,6 +58,7 @@ tasks because they govern other runtime packages:
| `live_ui.package.not_dsl_or_iur_owner` | `1.1.2` | `6.4.2` | Direct live_ui boundary obligation. |
| `live_ui.package.traceable_to_root_specs` | `6.4.1` | `6.4.2`, `6.3.2` | Direct live_ui documentation and governance obligation. |
| `live_ui.package.widget_component_library_surface` | `11.1` | `11.1.1` | Phase 11: Native surface is a mountable LiveComponent-oriented widget library. |
+| `live_ui.package.focused_example_specialization` | `18.1.1` | `18.1.2`, `18.2.1`, `18.3.2` | Phase 18: Package examples align one-for-one with the repository focused example inventory and specialize it for native live_ui review. |
### `structure.spec.md`
| Requirement ID | Primary plan coverage | Supporting coverage | Ownership note |
| --- | --- | --- | --- |
@@ -110,10 +111,11 @@ tasks because they govern other runtime packages:
### `tooling.spec.md`
| Requirement ID | Primary plan coverage | Supporting coverage | Ownership note |
| --- | --- | --- | --- |
-| `live_ui.tooling.reference_examples` | `6.1.1` | `2.4.1`, `4.4.1`, `5.4.1` | Direct tooling obligation. |
-| `live_ui.tooling.preview_and_inspection` | `6.2.1` | `5.3.1`, `5.3.2`, `6.3.2` | Direct tooling obligation. |
-| `live_ui.tooling.validation_workflow` | `6.3.1` | `4.3.1`, `5.5.1`, `6.5.2` | Direct tooling obligation. |
-| `live_ui.tooling.documentation_surface` | `6.4.1` | `6.4.2`, `6.5.2` | Direct tooling obligation. |
+| `live_ui.tooling.reference_examples` | `18.1.1` | `18.1.2`, `18.2.1`, `18.4.1` | Phase 18: Maintained reference examples align one-for-one with the repository focused example inventory. |
+| `live_ui.tooling.preview_and_inspection` | `18.2.1` | `5.3.1`, `5.3.2`, `6.3.2`, `18.2.2` | Phase 18: Preview and inspection workflows attach native, canonical, and transport review to the aligned focused example ids. |
+| `live_ui.tooling.validation_workflow` | `6.3.1` | `4.3.1`, `5.5.1`, `6.5.2`, `18.3.2`, `18.4.2` | Direct tooling obligation. |
+| `live_ui.tooling.documentation_surface` | `6.4.1` | `6.4.2`, `6.5.2`, `18.3.1`, `18.4.2` | Direct tooling obligation. |
+| `live_ui.tooling.no_package_local_demo_workbench` | `18.2.2` | `18.3.1`, `18.3.2`, `18.4.2` | Phase 18: Retire the package-local demo/workbench and divergent widget catalog in favor of aligned focused examples. |
## Upstream Canonical Input And Authoring Constraints
These specs are referenced by the planning index because they define the
canonical input surface and authored boundary that `live_ui` must consume or
diff --git a/.spec/specs/live_ui/package.spec.md b/.spec/specs/live_ui/package.spec.md
index edcae71d..6505fba9 100644
--- a/.spec/specs/live_ui/package.spec.md
+++ b/.spec/specs/live_ui/package.spec.md
@@ -28,6 +28,7 @@ surface:
decisions:
- repo.ecosystem.contract_model
- live_ui.runtime.widget_livecomponents
+ - live_ui.tooling.focused_example_alignment
```
## Requirements
@@ -62,6 +63,11 @@ decisions:
statement: The `live_ui` package subjects shall link back to the repository-level architecture, runtime-library, and signal transport subjects so package design remains subordinate to the root ecosystem contract.
priority: must
stability: stable
+
+- id: live_ui.package.focused_example_specialization
+ statement: The package review and maintainer example surface shall specialize the repository widget-focused example suite for direct native `live_ui` usage, aligning package example identities with the repository example inventory rather than maintaining a divergent package-local demo or unrelated example catalog.
+ priority: must
+ stability: stable
```
## Scenarios
@@ -85,5 +91,6 @@ decisions:
- live_ui.package.iur_renderer_entrypoint
- live_ui.package.not_dsl_or_iur_owner
- live_ui.package.traceable_to_root_specs
+ - live_ui.package.focused_example_specialization
- live_ui.package.direct_use_and_iur_use
```
diff --git a/.spec/specs/live_ui/tooling.spec.md b/.spec/specs/live_ui/tooling.spec.md
index 719e4665..6ff59834 100644
--- a/.spec/specs/live_ui/tooling.spec.md
+++ b/.spec/specs/live_ui/tooling.spec.md
@@ -21,18 +21,19 @@ surface:
- .spec/specs/live_ui/tooling.spec.md
decisions:
- repo.ecosystem.contract_model
+ - live_ui.tooling.focused_example_alignment
```
## Requirements
```spec-requirements
- id: live_ui.tooling.reference_examples
- statement: The package shall include maintained reference examples that demonstrate both direct native `live_ui` usage and canonical `unified_iur` rendering through the same runtime library.
+ statement: The package shall include maintained widget-focused reference examples aligned one-for-one with the repository `examples/` inventory, using direct native `live_ui` widgets and runtime entrypoints as the package-specialized example surface while preserving canonical `unified_iur` review paths against those same example identities where package validation requires them.
priority: must
stability: stable
- id: live_ui.tooling.preview_and_inspection
- statement: The package shall provide tooling or helper workflows that let maintainers inspect native widget rendering, canonical IUR rendering, styling behavior, and boundary event translation during package development.
+ statement: The package shall provide tooling or helper workflows that let maintainers inspect native widget rendering, canonical IUR rendering, styling behavior, and boundary event translation through the same focused example ids rather than through a separate package-local demo or divergent comparison catalog.
priority: must
stability: stable
@@ -45,6 +46,11 @@ decisions:
statement: The package shall document its native widget surface, LiveView runtime model, canonical IUR renderer entry point, and boundary event translation model as part of package development.
priority: must
stability: stable
+
+- id: live_ui.tooling.no_package_local_demo_workbench
+ statement: The package shall not maintain an aggregate package-local demo workbench or second widget catalog that diverges from the repository example suite; maintainer review shall begin from focused example applications aligned with the repository inventory.
+ priority: must
+ stability: stable
```
## Scenarios
@@ -53,7 +59,7 @@ decisions:
- id: live_ui.tooling_compare_native_and_iur_paths
given: A maintainer adds a new widget family or styling attribute to `live_ui`
when: The maintainer runs package tooling or reference workflows
- then: The maintainer can compare direct native rendering and canonical IUR rendering for the same feature area inside the package
+ then: The maintainer can review the aligned focused example for that widget, compare direct native rendering and canonical IUR rendering for the same example identity, and avoid creating a second package-only demo surface
```
## Verification
@@ -66,5 +72,6 @@ decisions:
- live_ui.tooling.preview_and_inspection
- live_ui.tooling.validation_workflow
- live_ui.tooling.documentation_surface
+ - live_ui.tooling.no_package_local_demo_workbench
- live_ui.tooling_compare_native_and_iur_paths
```
diff --git a/packages/live_ui/README.md b/packages/live_ui/README.md
index 83bf1c04..0b3e34ac 100644
--- a/packages/live_ui/README.md
+++ b/packages/live_ui/README.md
@@ -6,7 +6,6 @@ The package has two equally important roles:
- it is a directly usable native LiveView widget/runtime library
- it is the canonical `UnifiedIUR` renderer and boundary-transport layer for the LiveView runtime
-- it exposes a package-local demo workbench for maintained example review
`LiveUi` does not own the authored DSL or the canonical IUR data model. It owns
the server-authoritative runtime behavior that turns native screens and
@@ -20,7 +19,7 @@ canonical IUR values into one coherent LiveView-facing runtime.
- the shared server-authoritative runtime used for both native screens and canonical IUR rendering
- canonical `UnifiedIUR` rendering through native widget reuse instead of a second renderer stack
- canonical boundary transport via `Jido.Signal` and channel-safe translation helpers
-- maintained native, canonical, and mixed comparison examples
+- package-specialized `live_ui` screens that mirror the repository widget-focused example inventory one for one
- maintainer-facing preview, inspection, export, and validation workflows
`LiveUi` does not own:
@@ -32,9 +31,8 @@ canonical IUR values into one coherent LiveView-facing runtime.
## Maintainer Workflows
-The package includes five maintainer-facing Mix tasks:
+The package includes four maintainer-facing Mix tasks:
-- `mix live_ui.demo [home|EXAMPLE_ID] [--format summary|html|report|catalog]`
- `mix live_ui.preview [EXAMPLE_ID] [--format report|html|metadata]`
- `mix live_ui.inspect [EXAMPLE_ID] [--format report|metadata|comparison|diagnostics|catalog]`
- `mix live_ui.export [EXAMPLE_ID] [--format metadata|report|html|comparison|diagnostics|catalog]`
@@ -42,25 +40,31 @@ The package includes five maintainer-facing Mix tasks:
Use these commands to:
-- render the package-local demo workbench around maintained example lanes
-- launch the same demo as a real browser-hosted LiveView with `mix live_ui.demo --serve`
-- preview maintained native and canonical examples
-- inspect how native and canonical flows map onto the same runtime
+- preview the same focused example ids that the repository `examples/` suite exposes publicly
+- inspect how native and canonical flows map onto the same runtime for the same focused example id
- export review-friendly metadata, snapshots, comparisons, and diagnostics
-- validate continuity, transport, runtime authority, and documentation readiness
+- validate one-for-one inventory coverage, continuity, transport, runtime authority, and documentation readiness
-To run the browser demo locally:
+The package review story is anchored to the same focused example ids as the root suite. `live_ui` specializes those ids with native screens instead of maintaining a second catalog.
+
+Common entry points:
```bash
cd /Users/Pascal/code/unified/packages/live_ui
mix deps.get
-mix live_ui.demo --serve
+mix live_ui.preview button --format html
+mix live_ui.inspect button --format comparison
+mix live_ui.export button --format diagnostics
```
-By default the demo listens on [http://127.0.0.1:4040](http://127.0.0.1:4040). You can deep-link to a widget entry and change the port too:
+Canonical review, transport inspection, and continuity comparison stay attached to those same focused example ids instead of living in separate native/canonical/mixed lanes.
+
+The aligned focused example story covers the whole widget inventory. For example, `button`, `table`, `tabs`, and `tree_view` all resolve to package-local native `live_ui` screens, and the canonical review path stays available on the same focused example ids where package validation requires it.
+
+Before promoting runtime-boundary changes, run:
```bash
-mix live_ui.demo button --serve --port 4050
+mix live_ui.validate --strict
```
## Reference Guides
@@ -74,7 +78,7 @@ Use the package guides for the package contract details:
## Release Readiness
-`LiveUi` treats maintained example health, styled continuity alignment, boundary
-transport soundness, server-authoritative runtime behavior, and maintainer
-documentation as release-readiness criteria. Run `mix live_ui.validate --strict`
-before promoting runtime-boundary changes.
+`LiveUi` treats aligned example health, repository-inventory coverage, styled
+continuity alignment, boundary transport soundness, server-authoritative runtime
+behavior, and maintainer documentation as release-readiness criteria. Run
+`mix live_ui.validate --strict` before promoting runtime-boundary changes.
diff --git a/packages/live_ui/guides/canonical_rendering_and_transport.md b/packages/live_ui/guides/canonical_rendering_and_transport.md
index d08ee9e6..c53bd8c2 100644
--- a/packages/live_ui/guides/canonical_rendering_and_transport.md
+++ b/packages/live_ui/guides/canonical_rendering_and_transport.md
@@ -23,16 +23,18 @@ Boundary transport in `LiveUi` follows three related rules:
- channel envelopes must round-trip those canonical signals without losing runtime intent
The maintained boundary examples and validation report are the review baseline
-for transport changes:
+for transport changes. The canonical review path stays attached to the same
+aligned example ids that native maintainers already use:
-- `LiveUi.Examples.MixedBoundaryTransport`
-- `LiveUi.Examples.StyledContinuityComparison`
+- `mix live_ui.inspect button --format comparison`
+- `mix live_ui.export button --format diagnostics`
+- `mix live_ui.inspect table --format comparison`
## Review Guidance
When reviewing canonical renderer or transport changes, look for:
-- native/canonical continuity drift in paired styled examples
+- native/canonical continuity drift on the same aligned example ids
- missing canonical boundary signals for boundary-safe events
- renderer-local payload leakage into translated boundary signals
- regressions in server-authoritative runtime assumptions
diff --git a/packages/live_ui/guides/maintainer_workflows.md b/packages/live_ui/guides/maintainer_workflows.md
index 3e108ba8..5460118e 100644
--- a/packages/live_ui/guides/maintainer_workflows.md
+++ b/packages/live_ui/guides/maintainer_workflows.md
@@ -4,30 +4,29 @@ This guide covers the repeatable maintainer workflow for evolving `LiveUi`.
## Daily Package Review Workflow
-1. Review the package-local demo with `mix live_ui.demo`.
-2. When you need a real browser session, launch `mix live_ui.demo --serve`.
-3. Review the maintained example catalog with `mix live_ui.preview`.
-4. Inspect native or canonical example structure with `mix live_ui.inspect`.
-5. Export metadata, HTML, comparisons, or diagnostics with `mix live_ui.export`.
-6. Run `mix live_ui.validate` to review continuity, transport, and authority status.
-7. Run `mix live_ui.validate --strict` before treating a runtime-boundary change as release ready.
+1. Start from the same focused example ids that the repository `examples/` suite exposes publicly.
+2. Review native runtime output with `mix live_ui.preview button --format html`.
+3. Inspect native and canonical behavior for the same id with `mix live_ui.inspect button --format comparison`.
+4. Export diagnostics on the same id with `mix live_ui.export button --format diagnostics`.
+5. Run `mix live_ui.validate` to review coverage, continuity, transport, and authority status.
+6. Run `mix live_ui.validate --strict` before treating a runtime-boundary change as release ready.
## Adding a New Native Widget Family
When adding a new native widget family:
1. add the native widget component and tests
-2. add or update a maintained native example that exercises the new family
+2. add or update the aligned focused example with the same widget-focused id used by the repository suite
3. add the canonical renderer mapping if the construct is part of canonical `UnifiedIUR`
-4. add or update a paired continuity example when native/canonical behavior should stay aligned
+4. add or update canonical review coverage on that same example id when native/canonical behavior should stay aligned
5. rerun `mix live_ui.validate --strict`
## Reviewing Boundary Changes
When a change affects transport or canonical rendering:
-1. inspect the maintained mixed examples
-2. export comparison or diagnostics output for the affected example pair
+1. inspect the affected aligned example id with `mix live_ui.inspect ... --format comparison`
+2. export diagnostics output for that same aligned example id
3. confirm local-only flows remain local where intended
4. confirm boundary-safe flows still produce canonical `Jido.Signal` values
5. confirm the release-readiness report still passes in strict mode
@@ -38,5 +37,5 @@ Before merging a package-boundary change, review:
- whether a native widget change also needs a canonical renderer update
- whether a transport change alters canonical event meaning
-- whether a continuity pair now reports drift between native and canonical behavior
+- whether the same aligned example id now reports drift between native and canonical behavior
- whether the package docs still describe the maintained workflow accurately
diff --git a/packages/live_ui/guides/native_runtime_and_examples.md b/packages/live_ui/guides/native_runtime_and_examples.md
index 211c6221..aa9a71b2 100644
--- a/packages/live_ui/guides/native_runtime_and_examples.md
+++ b/packages/live_ui/guides/native_runtime_and_examples.md
@@ -12,29 +12,30 @@ The native package surface includes:
- native theme/style helpers through `LiveUi.Theme` and `LiveUi.Style`
- native screen definitions through `LiveUi.Screen`
-## Maintained Example Groups
+## Aligned Focused Example Inventory
-The maintained example catalog is intentionally split into three paths:
-
-- `:native`: directly authored runtime screens
-- `:canonical`: `UnifiedIUR` examples rendered through the shared runtime
-- `:mixed`: comparison and transport workflows that exercise both paths together
+The package example inventory follows the same widget-focused ids as the root
+`examples/` suite. Each aligned entry is a package-specialized live_ui screen
+for the same widget-focused example ids that the repository exposes publicly.
Every catalog entry exposes:
- a stable example `id`
+- a matching repository `root_example_id`
- a `preview_id` for maintainer tooling
- a `review_artifact` identity used in exports and review workflows
- `families` describing the feature areas the example covers
-- `coverage` metadata describing native/canonical/transport expectations
+- `coverage` metadata describing native/canonical/transport expectations on the same widget-focused example ids
## Preview and Inspection
Use the package tooling to review maintained examples:
-- `mix live_ui.preview native_styled_profile --format html`
-- `mix live_ui.inspect native_styled_operations --format comparison`
-- `mix live_ui.export styled_continuity_compare --format diagnostics`
+- `mix live_ui.preview button --format html`
+- `mix live_ui.inspect button --format comparison`
+- `mix live_ui.export button --format diagnostics`
These commands are designed to keep example review anchored to the package
-contract rather than one-off debug helpers.
+contract rather than one-off debug helpers. The same widget-focused example ids
+stay stable whether you are reviewing direct native rendering, a canonical
+review path, or transport diagnostics.
diff --git a/packages/live_ui/guides/runtime_backbone.md b/packages/live_ui/guides/runtime_backbone.md
index b4258e40..a08fafd4 100644
--- a/packages/live_ui/guides/runtime_backbone.md
+++ b/packages/live_ui/guides/runtime_backbone.md
@@ -26,5 +26,7 @@ That shared runtime is intentionally server-authoritative:
path, but canonical boundary translation must remain CloudEvents-compatible and
must not leak renderer-local payload semantics into boundary signals.
-Use the maintained mixed examples and `mix live_ui.validate` to review these
-transport expectations when the runtime boundary changes.
+Use the same aligned example ids and `mix live_ui.validate` to review these
+transport expectations when the runtime boundary changes. Native runtime review,
+canonical review, and transport inspection are all attached to the same aligned
+example ids instead of separate package-only lanes.
diff --git a/packages/live_ui/lib/live_ui.ex b/packages/live_ui/lib/live_ui.ex
index 37e64a23..d2ae31c9 100644
--- a/packages/live_ui/lib/live_ui.ex
+++ b/packages/live_ui/lib/live_ui.ex
@@ -4,7 +4,6 @@ defmodule LiveUi do
"""
alias LiveUi.{
- Demo,
Display,
Examples,
Forms,
@@ -25,11 +24,11 @@ defmodule LiveUi do
Widgets
}
- @type package_area :: :widgets | :runtime | :renderer | :transport | :tooling | :styling | :demo
+ @type package_area :: :widgets | :runtime | :renderer | :transport | :tooling | :styling
@spec package_areas() :: [package_area()]
def package_areas do
- [:widgets, :runtime, :renderer, :transport, :tooling, :styling, :demo]
+ [:widgets, :runtime, :renderer, :transport, :tooling, :styling]
end
@spec widgets() :: module()
@@ -80,9 +79,6 @@ defmodule LiveUi do
@spec tooling() :: module()
def tooling, do: Tooling
- @spec demo() :: module()
- def demo, do: Demo
-
@spec reference() :: map()
def reference, do: Reference.package_reference()
diff --git a/packages/live_ui/lib/live_ui/demo.ex b/packages/live_ui/lib/live_ui/demo.ex
deleted file mode 100644
index 54677c91..00000000
--- a/packages/live_ui/lib/live_ui/demo.ex
+++ /dev/null
@@ -1,168 +0,0 @@
-defmodule LiveUi.Demo do
- @moduledoc """
- Package-local example workbench for `live_ui`.
- """
-
- import Phoenix.LiveViewTest
-
- alias LiveUi.Demo.{Catalog, Screen, Server, WidgetPreviewState}
-
- @type view :: :home | :example
- @default_host "127.0.0.1"
- @default_port 4040
-
- @spec screen() :: module()
- def screen, do: Screen
-
- @spec default_host() :: String.t()
- def default_host, do: @default_host
-
- @spec default_port() :: pos_integer()
- def default_port, do: @default_port
-
- @spec catalog() :: map()
- def catalog do
- %{
- categories: Enum.map(Catalog.categories(), &Catalog.category_info/1),
- total_examples: Catalog.total_example_count(),
- path_counts: Catalog.path_counts()
- }
- end
-
- @spec screen_assigns(keyword()) :: {:ok, map()} | {:error, term()}
- def screen_assigns(opts \\ []) do
- initial_assigns(opts)
- end
-
- @spec path(keyword()) :: String.t()
- def path(opts \\ []) do
- base =
- case Keyword.get(opts, :example) do
- nil -> "/"
- example -> "/examples/" <> normalize_example_id(example)
- end
-
- case opts |> Keyword.get(:category) |> Catalog.normalize_category() do
- nil -> base
- category -> base <> "?category=" <> Atom.to_string(category)
- end
- end
-
- @spec serve(keyword()) :: {:ok, map()} | {:error, term()}
- def serve(opts \\ []) do
- host = Keyword.get(opts, :host, @default_host)
- port = Keyword.get(opts, :port, @default_port)
- launch_path = path(opts)
-
- case Server.start_link(host: host, port: port) do
- {:ok, server} ->
- {:ok,
- %{
- server: server,
- host: host,
- port: port,
- path: launch_path,
- url: Server.url(host: host, port: port, path: launch_path)
- }}
-
- other ->
- other
- end
- end
-
- @spec run(keyword()) :: {:ok, map()} | {:error, term()}
- def run(opts \\ []) do
- with {:ok, assigns} <- screen_assigns(opts),
- {:ok, runtime_state} <- LiveUi.Runtime.mount(Screen, assigns: assigns) do
- selected_example = Catalog.find_example(assigns.selected_example)
-
- {:ok,
- %{
- screen: Screen,
- view: assigns.view,
- selected_category: assigns.selected_category,
- selected_example: selected_example,
- categories: Enum.map(Catalog.categories(), &Catalog.category_info/1),
- examples: Catalog.category_examples(assigns.selected_category),
- total_examples: Catalog.total_example_count(),
- category_count: Catalog.category_count(),
- path_counts: Catalog.path_counts(),
- preview: maybe_preview(selected_example),
- html: render_runtime(runtime_state),
- event_routes: Map.keys(runtime_state.event_routes) |> Enum.sort()
- }}
- end
- end
-
- @spec html(keyword()) :: String.t()
- def html(opts \\ []) do
- {:ok, %{html: html}} = run(opts)
- html
- end
-
- defp normalize_example_id(example) do
- case Catalog.find_example(example) do
- %{id: id} -> to_string(id)
- _other -> to_string(example)
- end
- end
-
- defp initial_assigns(opts) do
- category =
- opts
- |> Keyword.get(:category)
- |> Catalog.normalize_category()
- |> Kernel.||(Catalog.default_category())
-
- case Keyword.get(opts, :example) do
- nil ->
- {:ok,
- base_assigns(category)
- |> Map.merge(%{
- view: :home,
- selected_category: category,
- selected_example: nil
- })}
-
- example_id ->
- with {:ok, example} <- Catalog.fetch_example(example_id) do
- {:ok,
- base_assigns(Catalog.primary_category(example.id) || category)
- |> Map.merge(%{
- view: :example,
- selected_category: Catalog.primary_category(example.id) || category,
- selected_example: example.id
- })}
- end
- end
- end
-
- defp maybe_preview(nil), do: nil
- defp maybe_preview(%{path: :widget}), do: nil
-
- defp maybe_preview(example) do
- case Catalog.preview(example.id) do
- {:ok, preview} -> preview
- {:error, reason} -> %{mode: :error, reason: reason}
- end
- end
-
- defp base_assigns(category) do
- %{
- selected_category: category,
- widget_demo_state: WidgetPreviewState.defaults(),
- canonical_interaction_hook: &WidgetPreviewState.apply/2
- }
- end
-
- defp render_runtime(runtime_state) do
- # Use render_component/3 from LiveViewTest to properly handle LiveComponents
- # The direct .render/1 approach doesn't work when the rendered content
- # contains nested LiveComponents (our widget components)
- render_component(LiveUi.Runtime.component(), %{
- id: "live-ui-demo-runtime",
- runtime_state: runtime_state,
- show_demo_panels: false
- })
- end
-end
diff --git a/packages/live_ui/lib/live_ui/demo/canonical_widget_preview.ex b/packages/live_ui/lib/live_ui/demo/canonical_widget_preview.ex
deleted file mode 100644
index 9f1ed40b..00000000
--- a/packages/live_ui/lib/live_ui/demo/canonical_widget_preview.ex
+++ /dev/null
@@ -1,335 +0,0 @@
-defmodule LiveUi.Demo.CanonicalWidgetPreview do
- @moduledoc false
-
- alias UnifiedIUR.{Interaction, Layout, Layer}
- alias UnifiedIUR.Widgets.{Advanced, Data, Foundational, Input, Navigation}
-
- @spec element(map(), map() | nil, String.t()) :: UnifiedIUR.Element.t() | nil
- def element(%{id: id}, demo_state, id_base) when is_binary(id_base) do
- demo_state = LiveUi.Demo.WidgetPreviewState.ensure_defaults(demo_state)
-
- case id do
- :button -> button_preview(demo_state, id_base)
- :link -> link_preview(demo_state, id_base)
- :text_input -> text_input_preview(demo_state, id_base)
- :toggle -> toggle_preview(demo_state, id_base)
- :select -> select_preview(demo_state, id_base)
- :menu -> menu_preview(demo_state, id_base)
- :tabs -> tabs_preview(demo_state, id_base)
- :list -> list_preview(demo_state, id_base)
- :table -> table_preview(demo_state, id_base)
- :tree_view -> tree_preview(demo_state, id_base)
- :context_menu -> context_menu_preview(demo_state, id_base)
- :command_palette -> command_palette_preview(demo_state, id_base)
- _other -> nil
- end
- end
-
- defp button_preview(demo_state, id_base) do
- clicks = demo_state.button.clicks
-
- preview_column(
- [
- Foundational.button("Run Widget Action",
- id: sample_id(id_base, "button"),
- action: [intent: :widget_demo_button]
- ),
- preview_text("Clicks recorded: #{clicks}", sample_id(id_base, "status"))
- ],
- id_base
- )
- end
-
- defp link_preview(demo_state, id_base) do
- clicks = demo_state.link.clicks
-
- preview_column(
- [
- Foundational.link("Trigger link interaction", "#",
- id: sample_id(id_base, "link"),
- interaction: Interaction.navigation(intent: :widget_demo_link)
- ),
- preview_text("Link activations: #{clicks}", sample_id(id_base, "status"))
- ],
- id_base
- )
- end
-
- defp text_input_preview(demo_state, id_base) do
- value = demo_state.text_input.value
-
- preview_column(
- [
- Input.text_input(
- id: sample_id(id_base, "text-input"),
- name: "widget_name",
- value: value,
- placeholder: "Widget name",
- interaction:
- Interaction.change(
- intent: :widget_demo_text_input,
- binding: :widget_name
- )
- ),
- preview_text("Current value: #{value}", sample_id(id_base, "status"))
- ],
- id_base
- )
- end
-
- defp toggle_preview(demo_state, id_base) do
- checked? = demo_state.toggle.checked
-
- preview_column(
- [
- Input.toggle(
- id: sample_id(id_base, "toggle"),
- name: "widget_enabled",
- value: checked?,
- interaction:
- Interaction.change(
- intent: :widget_demo_toggle,
- binding: :widget_enabled
- )
- ),
- preview_text("Toggle state: #{on_off(checked?)}", sample_id(id_base, "status"))
- ],
- id_base
- )
- end
-
- defp select_preview(demo_state, id_base) do
- current_value = demo_state.select.value
-
- preview_column(
- [
- Input.select(
- [
- %{id: "foundational", value: "foundational", label: "Foundational"},
- %{id: "display", value: "display", label: "Display", selected?: current_value == "display"},
- %{id: "overlay", value: "overlay", label: "Overlay"}
- ],
- id: sample_id(id_base, "select"),
- name: "widget_category",
- interaction:
- Interaction.change(
- intent: :widget_demo_select,
- binding: :widget_category
- )
- ),
- preview_text("Selected category: #{titleize(current_value)}", sample_id(id_base, "status"))
- ],
- id_base
- )
- end
-
- defp menu_preview(demo_state, id_base) do
- active_item = demo_state.menu.active
-
- preview_column(
- [
- Navigation.menu(
- [
- %{id: "overview", label: "Overview"},
- %{id: "insights", label: "Insights"},
- %{id: "settings", label: "Settings"}
- ],
- id: sample_id(id_base, "menu"),
- active_item: active_item,
- interaction: Interaction.click(intent: :widget_demo_menu)
- ),
- preview_text("Active item: #{titleize(active_item)}", sample_id(id_base, "status"))
- ],
- id_base
- )
- end
-
- defp tabs_preview(demo_state, id_base) do
- active_item = demo_state.tabs.active
-
- preview_column(
- [
- Navigation.tabs(
- [
- %{id: "surface", label: "Surface"},
- %{id: "state", label: "State"},
- %{id: "signals", label: "Signals"}
- ],
- id: sample_id(id_base, "tabs"),
- active_item: active_item,
- interaction: Interaction.selection(intent: :widget_demo_tabs)
- ),
- preview_text("Active tab: #{titleize(active_item)}", sample_id(id_base, "status"))
- ],
- id_base
- )
- end
-
- defp list_preview(demo_state, id_base) do
- selected_item = demo_state.list.selected
-
- preview_column(
- [
- Data.list(
- [
- %{
- id: "button",
- label: "Button",
- description: "Primary action surface",
- selected?: selected_item == "button"
- },
- %{
- id: "tabs",
- label: "Tabs",
- description: "Section navigation",
- selected?: selected_item == "tabs"
- },
- %{
- id: "toast",
- label: "Toast",
- description: "Transient feedback",
- selected?: selected_item == "toast"
- }
- ],
- id: sample_id(id_base, "list"),
- selection_mode: :single,
- interaction: Interaction.selection(intent: :widget_demo_list)
- ),
- preview_text("Selected row: #{titleize(selected_item)}", sample_id(id_base, "status"))
- ],
- id_base
- )
- end
-
- defp table_preview(demo_state, id_base) do
- selected_row = demo_state.table.selected
-
- preview_column(
- [
- Data.table(
- [
- %{id: "widget", label: "Widget"},
- %{id: "family", label: "Family"},
- %{id: "events", label: "Events"}
- ],
- [
- %{id: "button", cells: ["Button", "Content", "Click"], selected?: selected_row == "button"},
- %{id: "tabs", cells: ["Tabs", "Navigation", "Navigate"], selected?: selected_row == "tabs"},
- %{id: "toast", cells: ["Toast", "Overlay", "None"], selected?: selected_row == "toast"}
- ],
- id: sample_id(id_base, "table"),
- interaction: Interaction.selection(intent: :widget_demo_table)
- ),
- preview_text("Selected row: #{titleize(selected_row)}", sample_id(id_base, "status"))
- ],
- id_base
- )
- end
-
- defp tree_preview(demo_state, id_base) do
- selected_node = demo_state.tree_view.selected
-
- preview_column(
- [
- Data.tree_view(
- [
- %{
- id: "widgets",
- label: "Widgets",
- expanded?: true,
- children: [
- %{id: "content", label: "Content", selected?: selected_node == "content"},
- %{id: "overlay", label: "Overlay", selected?: selected_node == "overlay"}
- ]
- }
- ],
- id: sample_id(id_base, "tree-view"),
- selection_mode: :single,
- interaction: Interaction.selection(intent: :widget_demo_tree)
- ),
- preview_text("Selected node: #{titleize(selected_node)}", sample_id(id_base, "status"))
- ],
- id_base
- )
- end
-
- defp context_menu_preview(demo_state, id_base) do
- active_item = demo_state.context_menu.active
-
- preview_column(
- [
- Layer.context_menu(
- [
- %{id: "inspect", label: "Inspect"},
- %{id: "duplicate", label: "Duplicate"},
- %{id: "archive", label: "Archive"}
- ],
- id: sample_id(id_base, "context-menu"),
- active_item: active_item,
- anchor: %{x: 24, y: 24},
- interaction: Interaction.click(intent: :widget_demo_context_menu)
- ),
- preview_text("Selected action: #{titleize(active_item)}", sample_id(id_base, "status"))
- ],
- id_base
- )
- end
-
- defp command_palette_preview(demo_state, id_base) do
- query = demo_state.command_palette.query
- active_item = demo_state.command_palette.active
-
- preview_column(
- [
- Advanced.command_palette(
- [
- %{id: "widgets", label: "Open widgets", active: active_item == "widgets"},
- %{id: "workspace", label: "Toggle workspace", active: active_item == "workspace"},
- %{id: "validate", label: "Run validation", active: active_item == "validate"}
- ],
- id: sample_id(id_base, "command-palette"),
- query: query,
- active_command: active_item,
- interactions: [
- Interaction.change(
- intent: :widget_demo_command_query,
- binding: :command_query
- ),
- Interaction.selection(intent: :widget_demo_command_palette)
- ]
- ),
- preview_text(
- "Active command: #{titleize(active_item)} | Query: #{query}",
- sample_id(id_base, "status")
- )
- ],
- id_base
- )
- end
-
- defp preview_column(children, id_base) do
- Layout.column(children, id: sample_id(id_base, "column"), gap: :sm)
- end
-
- defp preview_text(text, id) do
- Foundational.text(text,
- id: id,
- style: %{foreground: "#cbd5e1"}
- )
- end
-
- defp sample_id(id_base, suffix), do: "#{id_base}-#{suffix}"
-
- defp on_off(true), do: "On"
- defp on_off(false), do: "Off"
-
- defp titleize(value) when is_binary(value) do
- value
- |> String.replace("_", " ")
- |> String.split()
- |> Enum.map_join(" ", &String.capitalize/1)
- end
-
- defp titleize(value) when is_atom(value), do: value |> Atom.to_string() |> titleize()
- defp titleize(nil), do: "None"
-end
diff --git a/packages/live_ui/lib/live_ui/demo/catalog.ex b/packages/live_ui/lib/live_ui/demo/catalog.ex
deleted file mode 100644
index c3329145..00000000
--- a/packages/live_ui/lib/live_ui/demo/catalog.ex
+++ /dev/null
@@ -1,302 +0,0 @@
-defmodule LiveUi.Demo.Catalog do
- @moduledoc """
- Catalog helpers for the package-local `live_ui` demo workbench.
- """
-
- alias LiveUi.{Component, Examples}
-
- @category_order [
- :foundational,
- :input,
- :navigation,
- :data,
- :feedback,
- :operational,
- :overlay,
- :display
- ]
-
- @category_profiles %{
- foundational: %{
- title: "Foundational",
- description: "Core building-block widgets for text, structure, and shell composition."
- },
- input: %{
- title: "Input",
- description: "Form and value-entry widgets for text, toggle, and selection flows."
- },
- navigation: %{
- title: "Navigation",
- description: "Widgets that move users between views, commands, and destinations."
- },
- data: %{
- title: "Data",
- description: "Widgets for lists, tables, trees, and document-like information surfaces."
- },
- feedback: %{
- title: "Feedback",
- description: "Status and chart widgets that explain progress, health, and change over time."
- },
- operational: %{
- title: "Operational",
- description: "Monitoring and runtime-observability widgets for systems-oriented screens."
- },
- overlay: %{
- title: "Overlay",
- description: "Dialog and layered-surface widgets for transient or elevated interactions."
- },
- display: %{
- title: "Display",
- description: "Viewport and canvas-oriented widgets for advanced presentation surfaces."
- }
- }
-
- @spec categories() :: [atom()]
- def categories, do: @category_order
-
- @spec default_category() :: atom()
- def default_category, do: :foundational
-
- @spec category_count() :: non_neg_integer()
- def category_count, do: length(categories())
-
- @spec total_example_count() :: non_neg_integer()
- def total_example_count, do: length(catalog())
-
- @spec path_counts() :: map()
- def path_counts do
- catalog()
- |> Enum.group_by(& &1.path)
- |> Enum.into(%{}, fn {path, entries} -> {path, length(entries)} end)
- end
-
- @spec catalog() :: [map()]
- def catalog do
- @category_order
- |> Enum.flat_map(&category_examples/1)
- end
-
- @spec normalize_category(atom() | String.t() | nil) :: atom() | nil
- def normalize_category(nil), do: nil
-
- def normalize_category(category) when is_atom(category) do
- if category in @category_order, do: category, else: nil
- end
-
- def normalize_category(category) when is_binary(category) do
- Enum.find(@category_order, &(Atom.to_string(&1) == category))
- end
-
- @spec category_info(atom()) :: map()
- def category_info(category) when category in @category_order do
- profile = Map.fetch!(@category_profiles, category)
- examples = category_examples(category)
-
- %{
- id: category,
- title: profile.title,
- description: profile.description,
- example_count: length(examples)
- }
- end
-
- @spec category_examples(atom()) :: [map()]
- def category_examples(category) when category in @category_order do
- category
- |> category_modules()
- |> Enum.map(&widget_example(&1, category))
- end
-
- @spec find_example(atom() | String.t() | nil) :: map() | nil
- def find_example(nil), do: nil
-
- def find_example(id) do
- wanted = to_string(id)
-
- Enum.find(catalog(), &(to_string(&1.id) == wanted)) || maintained_example(wanted)
- end
-
- @spec fetch_example(atom() | String.t()) :: {:ok, map()} | {:error, term()}
- def fetch_example(id) do
- case find_example(id) do
- nil -> {:error, :unknown_example}
- example -> {:ok, example}
- end
- end
-
- @spec primary_category(atom() | String.t() | map()) :: atom() | nil
- def primary_category(%{} = example) do
- Map.get(example, :primary_category, Map.get(example, :category))
- end
-
- def primary_category(id) do
- id
- |> find_example()
- |> primary_category()
- end
-
- @spec example_categories(map()) :: [atom()]
- def example_categories(%{} = example) do
- example
- |> primary_category()
- |> case do
- nil -> []
- category -> [category]
- end
- end
-
- @spec preview(atom() | String.t() | map()) :: {:ok, map()} | {:error, term()}
- def preview(example_or_id) do
- with {:ok, example} <- normalize_example(example_or_id) do
- preview_for(example)
- end
- end
-
- defp normalize_example(%{path: :widget} = example), do: {:ok, example}
-
- defp normalize_example(%{} = example) do
- {:ok, decorate_maintained_example(example)}
- end
-
- defp normalize_example(id) do
- fetch_example(id)
- end
-
- defp preview_for(%{path: :widget} = example) do
- {:ok,
- %{
- mode: :empty,
- example: example
- }}
- end
-
- defp preview_for(%{path: path, id: id}) when path in [:native, :canonical] do
- with {:ok, inspection} <- LiveUi.Tooling.preview_example(id) do
- {:ok,
- inspection.result
- |> Map.put(:mode, :html)
- |> Map.put(:runtime_mode, Map.get(inspection.result, :mode))
- |> Map.put(:native?, path == :native)
- |> Map.put(:canonical?, path == :canonical)}
- end
- end
-
- defp preview_for(%{path: :mixed, id: id} = example) do
- with {:ok, inspection} <- LiveUi.Tooling.inspect_example(id) do
- {:ok,
- %{
- mode: :report,
- example: example,
- report:
- inspect(inspection.result, pretty: true, width: 100, limit: :infinity, sort_maps: true)
- }}
- end
- end
-
- defp category_modules(:foundational), do: LiveUi.Widgets.Foundational.modules()
- defp category_modules(:input), do: LiveUi.Widgets.Input.modules()
- defp category_modules(:navigation), do: LiveUi.Widgets.Navigation.modules()
- defp category_modules(:data), do: LiveUi.Widgets.Data.modules()
- defp category_modules(:feedback), do: LiveUi.Widgets.Feedback.modules()
- defp category_modules(:operational), do: LiveUi.Widgets.Operational.modules()
- defp category_modules(:overlay), do: LiveUi.Widgets.Overlay.modules()
- defp category_modules(:display), do: LiveUi.Widgets.Display.modules()
-
- defp widget_example(module, category) do
- metadata = Component.metadata(module)
- category_title = category_title(category)
- family_title = titleize(metadata.family)
-
- %{
- id: metadata.name,
- title: titleize(metadata.name),
- module: module,
- path: :widget,
- category: category,
- categories: [category],
- primary_category: category,
- families: [metadata.family],
- comparable_to: nil,
- summary: widget_summary(metadata, category_title, family_title),
- preview_id: "widget:#{metadata.name}",
- review_artifact: "live_ui/widgets/#{metadata.name}",
- coverage: %{
- native?: false,
- canonical?: false,
- transport?: metadata.events != [],
- continuity?: false,
- advanced?: category in [:data, :feedback, :operational, :overlay, :display]
- },
- runtime_obligations: %{
- category: category,
- widget_family: metadata.family
- },
- component: %{
- family: metadata.family,
- name: metadata.name,
- assigns: metadata.assigns,
- slots: metadata.slots,
- style_hooks: metadata.style_hooks,
- events: metadata.events
- }
- }
- end
-
- defp widget_summary(metadata, category_title, family_title) do
- [
- family_sentence(family_title),
- slot_sentence(metadata.slots),
- event_sentence(metadata.events)
- ]
- |> Enum.reject(&is_nil/1)
- |> Enum.join(" ")
- |> then(fn details -> "#{category_title} widget. #{details}" end)
- end
-
- defp family_sentence(family_title), do: "Belongs to the #{family_title} family."
-
- defp slot_sentence([]), do: "No slots."
-
- defp slot_sentence(slots) do
- "Slots: " <> Enum.map_join(slots, ", ", &titleize/1) <> "."
- end
-
- defp event_sentence([]), do: "No runtime events."
-
- defp event_sentence(events) do
- "Events: " <> Enum.map_join(events, ", ", &titleize/1) <> "."
- end
-
- defp category_title(category) do
- @category_profiles
- |> Map.fetch!(category)
- |> Map.fetch!(:title)
- end
-
- defp titleize(value) when is_atom(value) do
- value
- |> Atom.to_string()
- |> titleize()
- end
-
- defp titleize(value) when is_binary(value) do
- value
- |> String.replace("_", " ")
- |> String.split()
- |> Enum.map_join(" ", &String.capitalize/1)
- end
-
- defp maintained_example(id) do
- case Examples.find(id) do
- {:ok, example} -> decorate_maintained_example(example)
- :error -> nil
- end
- end
-
- defp decorate_maintained_example(example) do
- example
- |> Map.put(:categories, [])
- |> Map.put(:primary_category, nil)
- |> Map.put(:hidden?, true)
- end
-end
diff --git a/packages/live_ui/lib/live_ui/demo/screen.ex b/packages/live_ui/lib/live_ui/demo/screen.ex
deleted file mode 100644
index 52300a5c..00000000
--- a/packages/live_ui/lib/live_ui/demo/screen.ex
+++ /dev/null
@@ -1,500 +0,0 @@
-defmodule LiveUi.Demo.Screen do
- @moduledoc """
- Package-local demo screen that turns the `live_ui` example catalog into a
- navigable workbench.
- """
-
- use LiveUi.Screen, id: :live_ui_demo, title: "Live UI Demo"
-
- alias LiveUi.Demo
- alias LiveUi.Demo.{Catalog, Style, WidgetPreview}
-
- @impl true
- def mount_defaults do
- %{
- view: :home,
- selected_category: Catalog.default_category(),
- selected_example: nil
- }
- end
-
- @impl true
- def render(assigns) do
- current_category =
- Catalog.normalize_category(assigns.selected_category) || Catalog.default_category()
-
- selected_example = Catalog.find_example(assigns.selected_example)
-
- assigns =
- assigns
- |> Map.put(:current_category, current_category)
- |> Map.put(:current_category_info, Catalog.category_info(current_category))
- |> Map.put(:selected_example_metadata, selected_example)
- |> Map.put(:preview, preview_for(selected_example))
- |> Map.put(:categories, Enum.map(Catalog.categories(), &Catalog.category_info/1))
- |> Map.put(:sidebar_examples, Catalog.category_examples(current_category))
- |> Map.put(:shell_style, Style.shell())
- |> Map.put(:stack_style, Style.layout(:column, "live-ui-demo-stack"))
- |> Map.put(:row_style, Style.layout(:row, "live-ui-demo-row"))
-
- ~H"""
-
-
- <%= browser_header(assigns) %>
-
- <%= sidebar(assigns) %>
- <%= content(assigns) %>
-
-
-
- """
- end
-
- def metadata do
- %{
- id: :live_ui_demo,
- title: title(),
- families: [:styling, :navigation, :comparison],
- comparable_to: nil,
- summary:
- "Package-local `live_ui` workbench for widget categories and package-owned widget inventory."
- }
- end
-
- defp browser_header(assigns) do
- assigns =
- assigns
- |> Map.put(:panel_style, Style.panel("live-ui-demo-browser-header"))
- |> Map.put(:kicker_style, Style.text("live-ui-demo-browser-kicker", tone: :accent))
- |> Map.put(:title_style, Style.text("live-ui-demo-browser-title", tone: :success))
- |> Map.put(:summary_style, Style.text("live-ui-demo-browser-summary"))
- |> Map.put(:header_top_style, Style.layout(:row, "live-ui-demo-browser-header-top"))
- |> Map.put(:header_copy_style, Style.layout(:column, "live-ui-demo-browser-copy"))
-
- ~H"""
-
- """
- end
-
- defp sidebar(assigns) do
- assigns =
- assigns
- |> Map.put(:panel_style, Style.panel("live-ui-demo-sidebar"))
- |> Map.put(:muted_style, Style.text("live-ui-demo-sidebar-copy"))
-
- ~H"""
-
- """
- end
-
- defp category_button(assigns, category) do
- button_style =
- if category.id == assigns.current_category do
- Style.button("live-ui-demo-category-button is-current", variant: :solid, state: :active)
- else
- Style.button("live-ui-demo-category-button", variant: :quiet)
- end
-
- assigns =
- assigns
- |> Map.put(:category, category)
- |> Map.put(:button_style, button_style)
- |> Map.put(:category_path, category_path(category.id))
-
- ~H"""
- <%= nav_link(
- "live-ui-demo-category-#{@category.id}",
- "#{@category.title} (#{@category.example_count})",
- @category_path,
- @button_style
- ) %>
- """
- end
-
- defp content(%{view: :example, selected_example_metadata: %{path: :widget}} = assigns) do
- example = assigns.selected_example_metadata
-
- assigns =
- assigns
- |> Map.put(:example, example)
- |> Map.put(:panel_style, Style.panel("live-ui-demo-content"))
- |> Map.put(:metadata_panel_style, Style.panel("live-ui-demo-example-metadata"))
- |> Map.put(:muted_style, Style.text("live-ui-demo-content-copy"))
- |> Map.put(:title_style, Style.text("live-ui-demo-content-title", tone: :accent))
- |> Map.put(
- :category_metadata,
- "Category: #{category_title(example.primary_category)} | Widget family: #{category_title(List.first(example.families))}"
- )
-
- ~H"""
-
- <%= category_examples_tabs(assigns) %>
-
-
-
-
-
-
-
-
-
- """
- end
-
- defp content(%{view: :example} = assigns) when not is_nil(assigns.selected_example_metadata) do
- example = assigns.selected_example_metadata
-
- assigns =
- assigns
- |> Map.put(:example, example)
- |> Map.put(:panel_style, Style.panel("live-ui-demo-content"))
- |> Map.put(:muted_style, Style.text("live-ui-demo-content-copy"))
- |> Map.put(:title_style, Style.text("live-ui-demo-content-title", tone: :accent))
- |> Map.put(:example_path_label, String.capitalize(to_string(example.path)))
- |> Map.put(:families_label, Enum.map_join(example.families, ", ", &category_title/1))
-
- ~H"""
-
- <%= category_examples_tabs(assigns) %>
-
-
-
- <%= preview_panel(assigns) %>
- <%= browser_style_panel(assigns) %>
-
- """
- end
-
- defp content(assigns) do
- assigns =
- assigns
- |> Map.put(:panel_style, Style.panel("live-ui-demo-home-panel"))
- |> Map.put(:muted_style, Style.text("live-ui-demo-home-copy"))
-
- ~H"""
-
- <%= category_examples_tabs(assigns) %>
-
-
-
-
-
- """
- end
-
- defp category_examples_tabs(assigns) do
- assigns =
- assigns
- |> Map.put(:panel_style, Style.panel("live-ui-demo-category-tabs-panel"))
- |> Map.put(:title_style, Style.text("live-ui-demo-category-tabs-title", tone: :accent))
- |> Map.put(:category_title, "Current category: #{assigns.current_category_info.title}")
- |> Map.put(
- :tabs_items,
- category_tab_items(assigns.sidebar_examples, assigns.current_category)
- )
- |> Map.put(:active_tab, active_category_tab(assigns.selected_example_metadata))
-
- ~H"""
-
-
-
-
-
- """
- end
-
- defp category_tab_items(examples, current_category) do
- Enum.map(examples, fn example ->
- %{
- id: to_string(example.id),
- label: example.title,
- patch: example_path(example.id, current_category)
- }
- end)
- end
-
- defp active_category_tab(nil), do: nil
- defp active_category_tab(example), do: to_string(example.id)
-
- defp preview_panel(%{preview: %{mode: :html} = preview} = assigns) do
- assigns =
- assigns
- |> Map.put(:preview_data, preview)
- |> Map.put(:panel_style, Style.panel("live-ui-demo-preview"))
- |> Map.put(:muted_style, Style.text("live-ui-demo-preview-copy"))
-
- ~H"""
-
-
-
- <%= Phoenix.HTML.raw(@preview_data.html) %>
-
- """
- end
-
- defp preview_panel(%{preview: %{mode: :report} = preview} = assigns) do
- assigns =
- assigns
- |> Map.put(:preview_data, preview)
- |> Map.put(:panel_style, Style.panel("live-ui-demo-preview"))
- |> Map.put(:muted_style, Style.text("live-ui-demo-preview-copy"))
-
- ~H"""
-
-
- <%= @preview_data.report %>
-
- """
- end
-
- defp preview_panel(_assigns), do: nil
-
- defp browser_style_panel(%{preview: %{browser_style: browser_style} = preview} = assigns) do
- assigns =
- assigns
- |> Map.put(:browser_style, browser_style)
- |> Map.put(:style_nodes, Enum.take(Map.get(preview, :browser_style_nodes, []), 4))
- |> Map.put(:panel_style, Style.panel("live-ui-demo-browser-style"))
- |> Map.put(:muted_style, Style.text("live-ui-demo-browser-style-copy"))
- |> Map.put(:title_style, Style.text("live-ui-demo-browser-style-title", tone: :success))
-
- ~H"""
-
-
- Enum.join(@browser_style.modes, ", ")}
- {@muted_style}
- />
- Enum.join(@browser_style.realized_fields, ", ")}
- {@muted_style}
- />
- Enum.join(@browser_style.css_var_keys, ", ")}
- {@muted_style}
- />
- empty_label(@browser_style.unsupported_fields)}
- {@muted_style}
- />
- empty_label(@browser_style.ignored_fields)}
- {@muted_style}
- />
-
- <%= for node <- @style_nodes do %>
- " <> Enum.join(node.css_var_keys, ", ")}
- {@muted_style}
- />
- <% end %>
-
- """
- end
-
- defp browser_style_panel(_assigns), do: nil
-
- defp preview_for(nil), do: nil
-
- defp preview_for(example) do
- case Catalog.preview(example.id) do
- {:ok, preview} -> preview
- {:error, reason} -> %{mode: :error, reason: reason}
- end
- end
-
- defp category_title(value) when is_atom(value) do
- value
- |> Atom.to_string()
- |> String.replace("_", " ")
- |> String.split()
- |> Enum.map_join(" ", &String.capitalize/1)
- end
-
- defp example_path(example, category) do
- Demo.path(example: example, category: category)
- end
-
- defp category_path(category) do
- Demo.path(category: category)
- end
-
- defp nav_link(id, label, path, style) do
- assigns = %{
- link_id: id,
- label: label,
- path: path,
- style: style,
- link_attrs: Map.get(style, :rest, %{}),
- tone: Map.get(style, :tone),
- variant: Map.get(style, :variant),
- state: Map.get(style, :state)
- }
-
- ~H"""
- <.link
- patch={@path}
- id={@link_id}
- class={@style.class}
- data-live-ui-widget="link"
- data-live-ui-tone={@tone}
- data-live-ui-variant={@variant}
- data-live-ui-state={@state}
- {@link_attrs}
- >
- <%= @label %>
-
- """
- end
-
- defp empty_label([]), do: "none"
- defp empty_label(values), do: Enum.join(values, ", ")
-end
diff --git a/packages/live_ui/lib/live_ui/demo/server.ex b/packages/live_ui/lib/live_ui/demo/server.ex
deleted file mode 100644
index 444bfef9..00000000
--- a/packages/live_ui/lib/live_ui/demo/server.ex
+++ /dev/null
@@ -1,68 +0,0 @@
-defmodule LiveUi.Demo.Server do
- @moduledoc """
- Minimal Phoenix host for running the package-local `live_ui` demo in a browser.
- """
-
- use Supervisor
-
- alias LiveUi.Demo.Server.Endpoint
-
- @pubsub __MODULE__.PubSub
- @secret_key_base "live_ui_demo_server_secret_key_base_live_ui_demo_server_secret_key_base"
- @signing_salt "live-ui-demo-signing-salt"
-
- @spec start_link(keyword()) :: Supervisor.on_start()
- def start_link(opts \\ []) do
- Supervisor.start_link(__MODULE__, opts)
- end
-
- @spec init(keyword()) :: {:ok, {:supervisor.sup_flags(), [Supervisor.child_spec()]}}
- def init(opts) do
- configure_endpoint(opts)
-
- children = [
- {Phoenix.PubSub, name: @pubsub},
- Endpoint
- ]
-
- Supervisor.init(children, strategy: :one_for_one)
- end
-
- @spec url(keyword()) :: String.t()
- def url(opts \\ []) do
- host = Keyword.get(opts, :host, LiveUi.Demo.default_host())
- port = Keyword.get(opts, :port, LiveUi.Demo.default_port())
- path = Keyword.get(opts, :path, "/")
-
- "http://#{host}:#{port}" <> normalize_path(path)
- end
-
- @spec pubsub() :: module()
- def pubsub, do: @pubsub
-
- defp configure_endpoint(opts) do
- host = Keyword.get(opts, :host, LiveUi.Demo.default_host())
- port = Keyword.get(opts, :port, LiveUi.Demo.default_port())
-
- Application.put_env(:live_ui, Endpoint,
- check_origin: false,
- code_reloader: false,
- debug_errors: false,
- http: [ip: bind_ip(host), port: port],
- live_view: [signing_salt: @signing_salt],
- pubsub_server: @pubsub,
- render_errors: [formats: [html: Phoenix.Controller], layout: false],
- root: Path.expand("../..", __DIR__),
- secret_key_base: @secret_key_base,
- server: true,
- url: [host: host, port: port]
- )
- end
-
- defp bind_ip("127.0.0.1"), do: {127, 0, 0, 1}
- defp bind_ip("localhost"), do: {127, 0, 0, 1}
- defp bind_ip(_other), do: {0, 0, 0, 0}
-
- defp normalize_path("/" <> _rest = path), do: path
- defp normalize_path(path), do: "/" <> path
-end
diff --git a/packages/live_ui/lib/live_ui/demo/server/endpoint.ex b/packages/live_ui/lib/live_ui/demo/server/endpoint.ex
deleted file mode 100644
index 4b83b620..00000000
--- a/packages/live_ui/lib/live_ui/demo/server/endpoint.ex
+++ /dev/null
@@ -1,45 +0,0 @@
-defmodule LiveUi.Demo.Server.Endpoint do
- @moduledoc false
-
- use Phoenix.Endpoint, otp_app: :live_ui
-
- alias LiveUi.Demo.Server.Router
-
- @session_options [
- store: :cookie,
- key: "_live_ui_demo_key",
- signing_salt: "live-ui-demo-session"
- ]
-
- socket("/live", Phoenix.LiveView.Socket,
- websocket: [connect_info: [session: @session_options]]
- )
-
- plug(Plug.Static,
- at: "/vendor/phoenix",
- from: {:phoenix, "priv/static"},
- gzip: false,
- only: ~w(phoenix.js)
- )
-
- plug(Plug.Static,
- at: "/vendor/live_view",
- from: {:phoenix_live_view, "priv/static"},
- gzip: false,
- only: ~w(phoenix_live_view.js)
- )
-
- plug(Plug.RequestId)
- plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])
-
- plug(Plug.Parsers,
- parsers: [:urlencoded, :multipart, :json],
- pass: ["*/*"],
- json_decoder: Phoenix.json_library()
- )
-
- plug(Plug.MethodOverride)
- plug(Plug.Head)
- plug(Plug.Session, @session_options)
- plug(Router)
-end
diff --git a/packages/live_ui/lib/live_ui/demo/server/layouts.ex b/packages/live_ui/lib/live_ui/demo/server/layouts.ex
deleted file mode 100644
index 6b2cad76..00000000
--- a/packages/live_ui/lib/live_ui/demo/server/layouts.ex
+++ /dev/null
@@ -1,491 +0,0 @@
-defmodule LiveUi.Demo.Server.Layouts do
- @moduledoc false
-
- use Phoenix.Component
-
- alias LiveUi.Stylesheet
-
- def root(var!(assigns)) do
- ~H"""
-
-
-
-
-
-
- <%= @page_title || "LiveUi Demo" %>
-
-
-
-
- <%= @inner_content %>
-
-
-
-
-
- """
- end
-
- defp css do
- ~S"""
- :root {
- --example-background: hsl(0 0% 4%);
- --example-foreground: hsl(0 0% 91%);
- --example-surface: hsl(0 0% 7%);
- --example-muted: hsl(0 0% 40%);
- --example-border: hsl(0 0% 16%);
- --example-border-strong: hsl(0 0% 23%);
- --example-primary: hsl(152 100% 50%);
- --example-primary-strong: hsl(152 100% 42%);
- --example-cyan: hsl(192 100% 50%);
- --example-yellow: hsl(43 100% 50%);
- --example-shadow: 0 20px 60px hsl(0 0% 0% / 0.45);
- --example-font: "IBM Plex Mono", "SFMono-Regular", "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;
- --gap-sm: 0.55rem;
- --gap-md: 0.85rem;
- --gap-lg: 1rem;
- --gap-xl: 1.35rem;
- }
-
- html {
- scroll-behavior: smooth;
- }
-
- body.unified-example-shell {
- margin: 0;
- min-height: 100vh;
- color: var(--example-foreground);
- background:
- radial-gradient(circle at top, hsl(152 100% 50% / 0.09), transparent 30%),
- radial-gradient(circle at 85% 15%, hsl(192 100% 50% / 0.1), transparent 24%),
- linear-gradient(180deg, hsl(0 0% 5%) 0%, var(--example-background) 100%);
- font-family: var(--example-font);
- }
-
- body.unified-example-shell::before {
- content: "";
- position: fixed;
- inset: 0;
- pointer-events: none;
- background-image:
- linear-gradient(hsl(0 0% 100% / 0.028) 1px, transparent 1px),
- linear-gradient(90deg, hsl(0 0% 100% / 0.028) 1px, transparent 1px);
- background-size: 36px 36px;
- mask-image: linear-gradient(180deg, hsl(0 0% 0% / 0.45), transparent 80%);
- }
-
- .example-app-shell {
- position: relative;
- z-index: 1;
- width: min(74rem, calc(100% - 2rem));
- margin: 0 auto;
- padding: 2.5rem 0 4rem;
- display: grid;
- gap: 1rem;
- }
-
- .live-ui-demo-browser-header-top {
- align-items: flex-start;
- justify-content: space-between;
- flex-wrap: wrap;
- }
-
- .live-ui-demo-browser-copy {
- flex: 1 1 24rem;
- min-width: 0;
- }
-
- .live-ui-demo-browser-kicker {
- color: var(--example-primary);
- text-transform: uppercase;
- letter-spacing: 0.16em;
- font-size: 0.72rem;
- font-weight: 700;
- }
-
- .live-ui-demo-browser-title {
- display: block;
- font-size: clamp(1.5rem, 3vw, 2.3rem);
- line-height: 1.08;
- letter-spacing: -0.03em;
- }
-
- .live-ui-demo-browser-summary,
- .live-ui-demo-browser-notes,
- .live-ui-demo-browser-status {
- display: block;
- max-width: 70ch;
- color: hsl(0 0% 91% / 0.82);
- line-height: 1.7;
- font-size: 0.98rem;
- }
-
- .live-ui-demo-browser-notes {
- color: var(--example-muted);
- }
-
- .live-ui-demo-browser-status {
- color: hsl(0 0% 91% / 0.88);
- }
-
- .live-ui-demo-sidebar-group {
- display: grid;
- gap: 0.7rem;
- }
-
- .live-ui-demo-category-tabs-title {
- color: var(--example-primary);
- font-size: 0.9rem;
- font-weight: 700;
- letter-spacing: 0.08em;
- text-transform: uppercase;
- }
-
- .live-ui-demo-category-tabs [role="tablist"] {
- display: flex;
- flex-wrap: wrap;
- gap: 0.65rem;
- }
-
- .live-ui-demo-category-tabs [role="tab"] {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- min-height: 2.45rem;
- padding: 0.6rem 0.9rem;
- border: 1px solid var(--example-border-strong);
- border-radius: 999px;
- background: hsl(0 0% 100% / 0.04);
- color: hsl(0 0% 91% / 0.78);
- text-decoration: none;
- font: inherit;
- font-size: 0.78rem;
- line-height: 1.2;
- cursor: pointer;
- transition:
- transform 120ms ease,
- border-color 160ms ease,
- background 160ms ease,
- color 160ms ease,
- box-shadow 160ms ease;
- }
-
- .live-ui-demo-category-tabs [role="tab"]:hover {
- transform: translateY(-1px);
- border-color: hsl(192 100% 50% / 0.28);
- color: hsl(0 0% 95%);
- }
-
- .live-ui-demo-category-tabs [role="tab"]:focus-visible {
- outline: 2px solid hsl(192 100% 50% / 0.9);
- outline-offset: 2px;
- }
-
- .live-ui-demo-category-tabs [role="tab"][aria-selected="true"] {
- color: hsl(0 0% 4%);
- background: linear-gradient(
- 180deg,
- hsl(152 100% 50% / 0.98) 0%,
- hsl(152 100% 42% / 0.92) 100%
- );
- border-color: hsl(152 100% 50% / 0.45);
- box-shadow:
- inset 0 1px 0 hsl(0 0% 100% / 0.2),
- 0 10px 26px hsl(152 100% 50% / 0.14);
- }
-
- .live-ui-demo-category-tabs [role="tab"][disabled] {
- opacity: 0.48;
- cursor: not-allowed;
- transform: none;
- }
-
- [data-live-ui-runtime="screen"] {
- display: grid;
- gap: 1rem;
- }
-
- [data-live-ui-widget="column"] {
- display: flex;
- flex-direction: column;
- min-width: 0;
- }
-
- [data-live-ui-widget="row"] {
- display: flex;
- flex-wrap: wrap;
- align-items: flex-start;
- min-width: 0;
- }
-
- [data-live-ui-widget="grid"] {
- display: grid;
- min-width: 0;
- }
-
- [data-live-ui-gap="sm"] {
- gap: var(--gap-sm);
- }
-
- [data-live-ui-gap="md"] {
- gap: var(--gap-md);
- }
-
- [data-live-ui-gap="lg"] {
- gap: var(--gap-lg);
- }
-
- [data-live-ui-gap="xl"] {
- gap: var(--gap-xl);
- }
-
- [data-live-ui-widget="grid"][data-live-ui-columns="2"] {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
-
- [data-live-ui-widget="grid"][data-live-ui-columns="3"] {
- grid-template-columns: repeat(3, minmax(0, 1fr));
- }
-
- .live-ui-box {
- box-sizing: border-box;
- }
-
- .live-ui-box.live-ui-box-panel,
- .live-ui-screen-shell {
- display: flex;
- flex-direction: column;
- gap: 1rem;
- border: 1px solid var(--example-border);
- border-radius: 16px;
- background:
- linear-gradient(180deg, hsl(0 0% 12% / 0.98) 0%, hsl(0 0% 10% / 0.98) 100%);
- box-shadow: inset 0 1px 0 hsl(0 0% 100% / 0.04);
- }
-
- .live-ui-screen-shell {
- padding: 1rem;
- }
-
- .live-ui-box.live-ui-box-panel {
- padding: 1.2rem;
- }
-
- .live-ui-text {
- color: var(--example-foreground);
- line-height: 1.6;
- }
-
- .live-ui-text[data-live-ui-tone="accent"],
- .live-ui-text[data-live-ui-tone="success"] {
- color: var(--example-primary);
- }
-
- .live-ui-button {
- appearance: none;
- border-radius: 12px;
- border: 1px solid var(--example-border-strong);
- padding: 0.8rem 1.15rem;
- min-height: 2.8rem;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 0.5rem;
- font: inherit;
- font-size: 0.92rem;
- font-weight: 700;
- letter-spacing: 0.02em;
- cursor: pointer;
- transition:
- transform 120ms ease,
- box-shadow 160ms ease,
- filter 160ms ease,
- border-color 160ms ease;
- }
-
- .live-ui-button:hover {
- transform: translateY(-1px);
- }
-
- .live-ui-button:focus-visible {
- outline: 2px solid hsl(192 100% 50% / 0.9);
- outline-offset: 2px;
- }
-
- .live-ui-button.live-ui-button-solid {
- color: hsl(0 0% 4%);
- background: linear-gradient(
- 180deg,
- hsl(152 100% 50% / 0.98) 0%,
- hsl(152 100% 42% / 0.92) 100%
- );
- border-color: hsl(152 100% 50% / 0.45);
- box-shadow:
- inset 0 1px 0 hsl(0 0% 100% / 0.22),
- 0 14px 34px hsl(152 100% 50% / 0.18);
- }
-
- .live-ui-button.live-ui-button-quiet {
- color: var(--example-primary);
- background: hsl(152 100% 50% / 0.08);
- border-color: hsl(152 100% 50% / 0.22);
- }
-
- #live-ui-demo-sidebar .live-ui-demo-button {
- width: 100%;
- display: flex;
- justify-content: flex-start;
- text-align: left;
- box-sizing: border-box;
- text-decoration: none;
- }
-
- .live-ui-text-input {
- width: 100%;
- min-height: 2.8rem;
- border-radius: 12px;
- border: 1px solid var(--example-border-strong);
- padding: 0.7rem 0.85rem;
- font: inherit;
- color: var(--example-foreground);
- background: hsl(0 0% 100% / 0.04);
- box-sizing: border-box;
- }
-
- .live-ui-demo-interaction-grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 1rem;
- align-items: stretch;
- }
-
- [data-live-ui-demo-story="true"] {
- margin-bottom: 1rem;
- border: 1px solid hsl(152 100% 50% / 0.18);
- border-radius: 14px;
- padding: 1rem;
- min-height: 6.75rem;
- background:
- linear-gradient(180deg, hsl(152 100% 50% / 0.08) 0%, hsl(0 0% 8% / 0.98) 100%);
- }
-
- [data-live-ui-signal-preview="true"] {
- border: 1px solid hsl(192 100% 50% / 0.18);
- border-radius: 14px;
- padding: 1rem;
- min-height: 6.75rem;
- background:
- linear-gradient(180deg, hsl(192 100% 50% / 0.06) 0%, hsl(0 0% 9% / 0.98) 100%);
- }
-
- [data-live-ui-demo-story="true"] h2,
- [data-live-ui-signal-preview="true"] h2 {
- margin: 0 0 0.75rem;
- font-size: 0.8rem;
- letter-spacing: 0.12em;
- text-transform: uppercase;
- }
-
- [data-live-ui-demo-story="true"] h2 {
- color: var(--example-primary);
- }
-
- [data-live-ui-signal-preview="true"] h2 {
- color: var(--example-cyan);
- }
-
- [data-live-ui-signal-status="true"],
- [data-live-ui-signal-empty="true"],
- [data-live-ui-demo-status="true"],
- [data-live-ui-demo-empty="true"],
- [data-live-ui-demo-outcome="true"],
- [data-live-ui-demo-payload="true"] {
- margin: 0.4rem 0 0;
- color: hsl(0 0% 91% / 0.78);
- line-height: 1.6;
- }
-
- [data-live-ui-signal-type="true"] {
- margin: 0.75rem 0 0.35rem;
- color: var(--example-primary);
- font-weight: 700;
- }
-
- [data-live-ui-runtime-event="true"] {
- margin: 0;
- color: var(--example-yellow);
- }
-
- [data-live-ui-signal-payload="true"],
- [data-live-ui-signal-translation="true"],
- [data-live-ui-runtime-event-error="true"],
- [data-live-ui-demo-error="true"],
- pre {
- margin: 0.85rem 0 0;
- padding: 0.8rem 0.9rem;
- overflow-x: auto;
- border: 1px solid var(--example-border);
- border-radius: 12px;
- background: hsl(0 0% 5%);
- color: hsl(0 0% 84%);
- font-size: 0.82rem;
- line-height: 1.55;
- box-sizing: border-box;
- }
-
- @media (min-width: 980px) {
- .live-ui-demo-row {
- flex-wrap: nowrap;
- }
-
- .live-ui-demo-row > :first-child {
- flex: 0 0 18rem;
- }
-
- .live-ui-demo-row > :last-child {
- flex: 1 1 auto;
- min-width: 0;
- }
- }
-
- @media (max-width: 720px) {
- .example-app-shell {
- width: min(100% - 1rem, 74rem);
- padding: 1rem 0 2rem;
- }
-
- .live-ui-box.live-ui-box-panel,
- .live-ui-screen-shell {
- border-radius: 14px;
- }
-
- [data-live-ui-widget="grid"][data-live-ui-columns="2"],
- [data-live-ui-widget="grid"][data-live-ui-columns="3"] {
- grid-template-columns: 1fr;
- }
-
- .live-ui-demo-interaction-grid {
- grid-template-columns: minmax(0, 1fr);
- }
- }
- """
- end
-end
diff --git a/packages/live_ui/lib/live_ui/demo/server/live.ex b/packages/live_ui/lib/live_ui/demo/server/live.ex
deleted file mode 100644
index 121bc214..00000000
--- a/packages/live_ui/lib/live_ui/demo/server/live.ex
+++ /dev/null
@@ -1,70 +0,0 @@
-defmodule LiveUi.Demo.Server.Live do
- @moduledoc false
-
- use Phoenix.LiveView
-
- alias LiveUi.Demo
-
- @impl true
- def mount(_params, _session, socket) do
- {:ok,
- socket
- |> assign(:runtime_state, nil)
- |> assign(:runtime_error, nil)
- |> assign(:page_title, "LiveUi Demo")}
- end
-
- @impl true
- def handle_params(params, _uri, socket) do
- opts = route_opts(socket.assigns.live_action, params)
-
- case Demo.screen_assigns(opts) do
- {:ok, assigns} ->
- {:ok, runtime_state} = LiveUi.Runtime.mount(Demo.screen(), assigns: assigns)
- selected_example = LiveUi.Demo.Catalog.find_example(assigns.selected_example)
-
- {:noreply,
- socket
- |> assign(:runtime_state, runtime_state)
- |> assign(:runtime_error, nil)
- |> assign(:page_title, page_title(selected_example))}
-
- {:error, reason} ->
- {:noreply,
- socket
- |> assign(:runtime_state, nil)
- |> assign(:runtime_error, reason)
- |> assign(:selected_example, nil)
- |> assign(:page_title, "LiveUi Demo")}
- end
- end
-
- @impl true
- def render(assigns) do
- ~H"""
-
- <%= if @runtime_state do %>
- <.live_component
- module={LiveUi.Runtime.component()}
- id="live-ui-demo-runtime"
- runtime_state={@runtime_state}
- show_demo_panels={false}
- />
- <% else %>
- <%= inspect(@runtime_error, pretty: true) %>
- <% end %>
-
- """
- end
-
- defp route_opts(:home, params) do
- [category: Map.get(params, "category")]
- end
-
- defp route_opts(:example, params) do
- [category: Map.get(params, "category"), example: Map.get(params, "example_id")]
- end
-
- defp page_title(nil), do: "LiveUi Demo"
- defp page_title(example), do: "#{example.title} | LiveUi Demo"
-end
diff --git a/packages/live_ui/lib/live_ui/demo/server/router.ex b/packages/live_ui/lib/live_ui/demo/server/router.ex
deleted file mode 100644
index 39edeb0c..00000000
--- a/packages/live_ui/lib/live_ui/demo/server/router.ex
+++ /dev/null
@@ -1,25 +0,0 @@
-defmodule LiveUi.Demo.Server.Router do
- @moduledoc false
-
- use Phoenix.Router
-
- import Phoenix.LiveView.Router
-
- alias LiveUi.Demo.Server.{Layouts, Live}
-
- pipeline :browser do
- plug(:accepts, ["html"])
- plug(:fetch_session)
- plug(:fetch_live_flash)
- plug(:put_root_layout, html: {Layouts, :root})
- plug(:protect_from_forgery)
- plug(:put_secure_browser_headers)
- end
-
- scope "/" do
- pipe_through(:browser)
-
- live("/", Live, :home, as: :demo)
- live("/examples/:example_id", Live, :example, as: :demo)
- end
-end
diff --git a/packages/live_ui/lib/live_ui/demo/style.ex b/packages/live_ui/lib/live_ui/demo/style.ex
deleted file mode 100644
index 9acd25ae..00000000
--- a/packages/live_ui/lib/live_ui/demo/style.ex
+++ /dev/null
@@ -1,87 +0,0 @@
-defmodule LiveUi.Demo.Style do
- @moduledoc """
- Shared style helpers for the package-local `live_ui` demo.
- """
-
- @theme LiveUi.Theme.default()
-
- @spec shell() :: map()
- def shell do
- LiveUi.Style.component_assigns(:screen_shell,
- theme: @theme,
- variant: :workspace,
- class: "live-ui-demo-shell"
- )
- end
-
- @spec panel(String.t() | nil) :: map()
- def panel(class \\ nil) do
- LiveUi.Style.component_assigns(:box,
- theme: @theme,
- variant: :panel,
- class: merge_classes(["live-ui-demo-panel", class])
- )
- end
-
- @spec text(String.t() | nil, keyword()) :: map()
- def text(class \\ nil, opts \\ []) do
- LiveUi.Style.component_assigns(:text,
- theme: @theme,
- tone: Keyword.get(opts, :tone),
- class: merge_classes(["live-ui-demo-text", class])
- )
- end
-
- @spec button(String.t() | nil, keyword()) :: map()
- def button(class \\ nil, opts \\ []) do
- LiveUi.Style.component_assigns(:button,
- theme: @theme,
- variant: Keyword.get(opts, :variant, :solid),
- state: Keyword.get(opts, :state),
- class: merge_classes(["live-ui-demo-button", class])
- )
- end
-
- @spec link(String.t() | nil, keyword()) :: map()
- def link(class \\ nil, opts \\ []) do
- LiveUi.Style.component_assigns(:link,
- theme: @theme,
- tone: Keyword.get(opts, :tone),
- variant: Keyword.get(opts, :variant),
- state: Keyword.get(opts, :state),
- class: merge_classes(["live-ui-demo-link", class])
- )
- end
-
- @spec layout(atom(), String.t() | nil) :: map()
- def layout(component, class \\ nil) do
- LiveUi.Style.component_assigns(component,
- theme: @theme,
- class: merge_classes(["live-ui-demo-#{component}", class])
- )
- end
-
- @spec direct(atom(), keyword()) :: map()
- def direct(component, opts \\ []) do
- LiveUi.Style.component_assigns(component,
- theme: @theme,
- tone: Keyword.get(opts, :tone),
- variant: Keyword.get(opts, :variant),
- state: Keyword.get(opts, :state),
- attrs: Keyword.get(opts, :attrs, %{}),
- style: Keyword.get(opts, :style),
- class: Keyword.get(opts, :class)
- )
- end
-
- defp merge_classes(classes) do
- classes
- |> List.flatten()
- |> Enum.reject(&(&1 in [nil, ""]))
- |> Enum.join(" ")
- |> case do
- "" -> nil
- value -> value
- end
- end
-end
diff --git a/packages/live_ui/lib/live_ui/demo/widget_preview.ex b/packages/live_ui/lib/live_ui/demo/widget_preview.ex
deleted file mode 100644
index f0eb4c99..00000000
--- a/packages/live_ui/lib/live_ui/demo/widget_preview.ex
+++ /dev/null
@@ -1,909 +0,0 @@
-defmodule LiveUi.Demo.WidgetPreview do
- @moduledoc false
-
- use Phoenix.Component
-
- alias LiveUi.Demo.{CanonicalWidgetPreview, Style}
-
- attr(:example, :map, required: true)
- attr(:demo_state, :map, default: %{})
- attr(:event_target, :string, default: nil)
-
- @sample_image_uri "data:image/svg+xml;utf8,"
-
- def render(assigns) do
- assigns =
- assigns
- |> assign(:id_base, "live-ui-demo-widget-#{assigns.example.id}")
- |> assign(:panel_style, Style.direct(:box, variant: :panel))
- |> assign(:title_style, Style.direct(:text, tone: :accent))
- |> assign(
- :canonical_element,
- CanonicalWidgetPreview.element(assigns.example, assigns.demo_state, "live-ui-demo-widget-#{assigns.example.id}")
- )
-
- ~H"""
-
-
- <%= if @canonical_element do %>
-
- <% else %>
- <%= preview(assigns) %>
- <% end %>
-
- """
- end
-
- defp preview(%{example: %{id: :text}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:text, tone: :accent))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :label}} = assigns) do
- assigns =
- assigns
- |> assign(:label_style, Style.direct(:label))
- |> assign(:input_style, Style.direct(:text_input, variant: :filled))
-
- ~H"""
-
-
-
-
- """
- end
-
- defp preview(%{example: %{id: :image}} = assigns) do
- assigns =
- assigns
- |> assign(:style, Style.direct(:image))
- |> assign(:sample_image_uri, @sample_image_uri)
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :icon}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:icon, tone: :accent))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :button}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:button, variant: :quiet))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :link}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:link, tone: :accent))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :separator}} = assigns) do
- assigns =
- assigns
- |> assign(:text_style, Style.direct(:text))
- |> assign(:separator_style, Style.direct(:separator))
-
- ~H"""
-
-
-
-
-
- """
- end
-
- defp preview(%{example: %{id: :spacer}} = assigns) do
- assigns =
- assigns
- |> assign(:text_style, Style.direct(:text))
- |> assign(:spacer_style, Style.direct(:spacer))
-
- ~H"""
-
-
-
-
-
- """
- end
-
- defp preview(%{example: %{id: :content}} = assigns) do
- assigns =
- assigns
- |> assign(:content_style, Style.direct(:content))
- |> assign(:text_style, Style.direct(:text))
-
- ~H"""
-
-
-
- """
- end
-
- defp preview(%{example: %{id: :container}} = assigns) do
- assigns =
- assigns
- |> assign(:container_style, Style.direct(:container))
- |> assign(:text_style, Style.direct(:text))
-
- ~H"""
-
-
-
- """
- end
-
- defp preview(%{example: %{id: :box}} = assigns) do
- assigns =
- assigns
- |> assign(:box_style, Style.direct(:box, variant: :panel))
- |> assign(:text_style, Style.direct(:text))
-
- ~H"""
-
-
-
- """
- end
-
- defp preview(%{example: %{id: :screen_shell}} = assigns) do
- assigns =
- assigns
- |> assign(:shell_style, Style.direct(:screen_shell, variant: :workspace))
- |> assign(:text_style, Style.direct(:text))
-
- ~H"""
-
-
-
- """
- end
-
- defp preview(%{example: %{id: :text_input}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:text_input, variant: :filled))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :toggle}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:toggle))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :select}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:select))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :menu}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:menu))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :tabs}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:tabs))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :command_palette}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:command_palette))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :list}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:list))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :table}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:table))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :tree_view}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:tree_view))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :markdown_viewer}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:markdown_viewer))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :log_viewer}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:log_viewer))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :status}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:status))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :progress}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:progress))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :gauge}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:gauge))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :inline_feedback}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:inline_feedback))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :sparkline}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:sparkline))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :bar_chart}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:bar_chart))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :line_chart}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:line_chart))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :stream_widget}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:stream_widget))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :process_monitor}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:process_monitor))
-
- ~H"""
- ", state: :running},
- %{id: "theme", pid: "#PID<0.322.0>", state: :idle},
- %{id: "preview", pid: "#PID<0.323.0>", state: :waiting}
- ]}
- {@style}
- />
- """
- end
-
- defp preview(%{example: %{id: :supervision_tree_viewer}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:supervision_tree_viewer))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :cluster_dashboard}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:cluster_dashboard))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :overlay_surface}} = assigns) do
- assigns =
- assigns
- |> assign(:surface_style, Style.direct(:overlay_surface, variant: :modal))
- |> assign(:box_style, Style.direct(:box, variant: :panel))
- |> assign(:toast_style, Style.direct(:toast, state: :active))
- |> assign(:text_style, Style.direct(:text))
-
- ~H"""
-
- <:base>
-
-
-
-
- <:overlay>
-
-
-
-
-
- """
- end
-
- defp preview(%{example: %{id: :dialog}} = assigns) do
- assigns =
- assigns
- |> assign(:style, Style.direct(:dialog, variant: :modal))
- |> assign(:button_style, Style.direct(:button, variant: :quiet))
- |> assign(:text_style, Style.direct(:text))
-
- ~H"""
-
-
- <:actions>
-
-
-
- """
- end
-
- defp preview(%{example: %{id: :alert_dialog}} = assigns) do
- assigns =
- assigns
- |> assign(:style, Style.direct(:alert_dialog))
- |> assign(:button_style, Style.direct(:button, variant: :quiet))
- |> assign(:text_style, Style.direct(:text))
-
- ~H"""
-
-
- <:actions>
-
-
-
- """
- end
-
- defp preview(%{example: %{id: :context_menu}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:context_menu))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :toast}} = assigns) do
- assigns =
- assigns
- |> assign(:style, Style.direct(:toast, state: :active))
- |> assign(:text_style, Style.direct(:text))
-
- ~H"""
-
-
-
- """
- end
-
- defp preview(%{example: %{id: :viewport}} = assigns) do
- assigns =
- assigns
- |> assign(:style, Style.direct(:viewport))
- |> assign(:text_style, Style.direct(:text))
-
- ~H"""
-
-
- <%= for index <- 1..6 do %>
-
- <% end %>
-
-
- """
- end
-
- defp preview(%{example: %{id: :scroll_bar}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:scroll_bar))
-
- ~H"""
-
- """
- end
-
- defp preview(%{example: %{id: :split_pane}} = assigns) do
- assigns =
- assigns
- |> assign(:style, Style.direct(:split_pane))
- |> assign(:box_style, Style.direct(:box, variant: :panel))
- |> assign(:text_style, Style.direct(:text))
-
- ~H"""
-
- <:primary>
-
-
-
-
- <:secondary>
-
-
-
-
-
- """
- end
-
- defp preview(%{example: %{id: :canvas}} = assigns) do
- assigns = assign(assigns, :style, Style.direct(:canvas, variant: :analysis))
-
- ~H"""
-
- """
- end
-
- defp sample_id(base, suffix), do: "#{base}-#{suffix}"
-end
diff --git a/packages/live_ui/lib/live_ui/demo/widget_preview_state.ex b/packages/live_ui/lib/live_ui/demo/widget_preview_state.ex
deleted file mode 100644
index ea6fbf8a..00000000
--- a/packages/live_ui/lib/live_ui/demo/widget_preview_state.ex
+++ /dev/null
@@ -1,163 +0,0 @@
-defmodule LiveUi.Demo.WidgetPreviewState do
- @moduledoc false
-
- alias LiveUi.Runtime.State
-
- @type widget_id ::
- :button
- | :link
- | :text_input
- | :toggle
- | :select
- | :menu
- | :tabs
- | :list
- | :table
- | :tree_view
- | :context_menu
- | :command_palette
-
- @type t :: %{
- optional(widget_id()) => map()
- }
-
- @spec defaults() :: t()
- def defaults do
- %{
- button: %{clicks: 0},
- link: %{clicks: 0},
- text_input: %{value: "Live UI"},
- toggle: %{checked: true},
- select: %{value: "display"},
- menu: %{active: "insights"},
- tabs: %{active: "surface"},
- list: %{selected: "button"},
- table: %{selected: "button"},
- tree_view: %{selected: "overlay"},
- context_menu: %{active: "inspect"},
- command_palette: %{query: "wid", active: "widgets"}
- }
- end
-
- @spec apply(State.t(), map()) :: State.t()
- def apply(%State{} = runtime_state, translation) when is_map(translation) do
- case normalize_widget_id(runtime_state.assigns[:selected_example]) do
- nil ->
- runtime_state
-
- widget_id ->
- demo_state =
- runtime_state.assigns
- |> Map.get(:widget_demo_state, defaults())
- |> ensure_defaults()
- |> update_widget_state(widget_id, normalize_payload(translation))
-
- %{runtime_state | assigns: Map.put(runtime_state.assigns, :widget_demo_state, demo_state)}
- end
- end
-
- @spec ensure_defaults(map() | nil) :: t()
- def ensure_defaults(nil), do: defaults()
-
- def ensure_defaults(state) when is_map(state) do
- Map.merge(defaults(), state)
- end
-
- defp update_widget_state(state, :button, _payload) do
- update_in(state, [:button, :clicks], &((&1 || 0) + 1))
- end
-
- defp update_widget_state(state, :link, _payload) do
- update_in(state, [:link, :clicks], &((&1 || 0) + 1))
- end
-
- defp update_widget_state(state, :text_input, payload) do
- put_in(state, [:text_input, :value], fetch(payload, "widget_name", state[:text_input][:value]))
- end
-
- defp update_widget_state(state, :toggle, payload) do
- checked? =
- payload
- |> fetch("widget_enabled")
- |> checkbox_value?()
-
- put_in(state, [:toggle, :checked], checked?)
- end
-
- defp update_widget_state(state, :select, payload) do
- value = fetch(payload, "widget_category", state[:select][:value])
- put_in(state, [:select, :value], value)
- end
-
- defp update_widget_state(state, :menu, payload) do
- put_in(state, [:menu, :active], fetch(payload, "item_id", state[:menu][:active]))
- end
-
- defp update_widget_state(state, :tabs, payload) do
- put_in(state, [:tabs, :active], fetch(payload, "item_id", state[:tabs][:active]))
- end
-
- defp update_widget_state(state, :list, payload) do
- put_in(state, [:list, :selected], fetch(payload, "item_id", state[:list][:selected]))
- end
-
- defp update_widget_state(state, :table, payload) do
- put_in(state, [:table, :selected], fetch(payload, "item_id", state[:table][:selected]))
- end
-
- defp update_widget_state(state, :tree_view, payload) do
- put_in(state, [:tree_view, :selected], fetch(payload, "item_id", state[:tree_view][:selected]))
- end
-
- defp update_widget_state(state, :context_menu, payload) do
- put_in(
- state,
- [:context_menu, :active],
- fetch(payload, "item_id", state[:context_menu][:active])
- )
- end
-
- defp update_widget_state(state, :command_palette, payload) do
- state
- |> put_in(
- [:command_palette, :query],
- fetch(payload, "command_query", state[:command_palette][:query])
- )
- |> put_in(
- [:command_palette, :active],
- fetch(payload, "item_id", state[:command_palette][:active])
- )
- end
-
- defp update_widget_state(state, _widget_id, _payload), do: state
-
- defp normalize_widget_id(nil), do: nil
-
- defp normalize_widget_id(widget_id) when is_atom(widget_id) do
- if widget_id in Map.keys(defaults()), do: widget_id, else: nil
- end
-
- defp normalize_widget_id(widget_id) when is_binary(widget_id) do
- widget_id
- |> String.to_existing_atom()
- |> normalize_widget_id()
- rescue
- ArgumentError -> nil
- end
-
- defp normalize_payload(translation) do
- translation
- |> Map.get(:payload, %{})
- |> case do
- payload when is_map(payload) -> Map.new(payload, fn {key, value} -> {to_string(key), value} end)
- _other -> %{}
- end
- end
-
- defp fetch(payload, key, default \\ nil) do
- Map.get(payload, key, default)
- end
-
- defp checkbox_value?(value) when value in [true, "true", "on", "1", 1], do: true
- defp checkbox_value?(_value), do: false
-end
diff --git a/packages/live_ui/lib/live_ui/examples.ex b/packages/live_ui/lib/live_ui/examples.ex
index f753f024..88a5f4cb 100644
--- a/packages/live_ui/lib/live_ui/examples.ex
+++ b/packages/live_ui/lib/live_ui/examples.ex
@@ -1,111 +1,62 @@
defmodule LiveUi.Examples do
@moduledoc """
- Maintained baseline native and canonical examples for `live_ui`.
+ Maintained aligned example inventory for `live_ui`.
"""
- @type example_path :: :native | :canonical | :mixed
+ alias LiveUi.Examples.Aligned
- @native_examples [
- LiveUi.Examples.NativeDisplayScreen,
- LiveUi.Examples.NativeFormScreen,
- LiveUi.Examples.NativeNavigationScreen,
- LiveUi.Examples.NativeBoundaryScreen,
- LiveUi.Examples.NativeStyledProfileScreen,
- LiveUi.Examples.NativeStyledOperationsScreen
- ]
-
- @canonical_examples [
- LiveUi.Examples.CanonicalDisplay,
- LiveUi.Examples.CanonicalForm,
- LiveUi.Examples.CanonicalNavigation,
- LiveUi.Examples.CanonicalBoundaryProfile,
- LiveUi.Examples.CanonicalStyledProfile,
- LiveUi.Examples.CanonicalStyledOperations
- ]
-
- @mixed_examples [
- LiveUi.Examples.MixedBoundaryTransport,
- LiveUi.Examples.StyledContinuityComparison,
- LiveUi.Examples.WebNavigationTransitionComparison
- ]
-
- @spec native_examples() :: [module()]
- def native_examples do
- @native_examples
+ @spec catalog() :: [map()]
+ def catalog do
+ aligned_catalog()
end
- @spec canonical_examples() :: [module()]
- def canonical_examples do
- @canonical_examples
+ @spec aligned_example_ids() :: [atom()]
+ def aligned_example_ids do
+ Aligned.ids()
end
- @spec mixed_examples() :: [module()]
- def mixed_examples do
- @mixed_examples
+ @spec aligned_modules() :: [module()]
+ def aligned_modules do
+ Aligned.modules()
end
- @spec grouped_catalog() :: %{native: [map()], canonical: [map()], mixed: [map()]}
- def grouped_catalog do
- %{
- native: Enum.map(native_examples(), &example_metadata(&1, :native)),
- canonical: Enum.map(canonical_examples(), &example_metadata(&1, :canonical)),
- mixed: Enum.map(mixed_examples(), &example_metadata(&1, :mixed))
- }
+ @spec aligned_catalog() :: [map()]
+ def aligned_catalog do
+ Aligned.catalog()
end
- @spec catalog() :: [map()]
- def catalog do
- grouped_catalog().native ++ grouped_catalog().canonical ++ grouped_catalog().mixed
+ @spec repository_example_ids() :: [atom()]
+ def repository_example_ids do
+ Aligned.repository_example_ids()
end
@spec find(atom() | String.t()) :: {:ok, map()} | :error
def find(id) when is_atom(id) or is_binary(id) do
- wanted = to_string(id)
+ Aligned.find(id)
+ end
- catalog()
- |> Enum.find(&(to_string(&1.id) == wanted))
- |> case do
- nil -> :error
- example -> {:ok, example}
- end
+ @spec find_aligned(atom() | String.t()) :: {:ok, map()} | :error
+ def find_aligned(id) when is_atom(id) or is_binary(id) do
+ find(id)
end
- defp example_metadata(module, path) do
- metadata = module.metadata()
- preview_id = "#{path}:#{metadata.id}"
+ @spec canonical_review_supported?(atom() | String.t()) :: boolean()
+ def canonical_review_supported?(id) do
+ Aligned.canonical_review_supported?(id)
+ end
- %{
- id: metadata.id,
- title: metadata.title,
- module: module,
- path: path,
- families: metadata.families,
- comparable_to: metadata.comparable_to,
- summary: metadata.summary,
- preview_id: preview_id,
- review_artifact: "live_ui/#{path}/#{metadata.id}",
- coverage: coverage_metadata(path, metadata),
- runtime_obligations: runtime_obligations(path, metadata)
- }
+ @spec canonical_review_ids() :: [atom()]
+ def canonical_review_ids do
+ Aligned.canonical_review_ids()
end
- defp coverage_metadata(path, metadata) do
- %{
- native?: path in [:native, :mixed],
- canonical?: path in [:canonical, :mixed],
- transport?: :transport in metadata.families or :signal in metadata.families,
- continuity?: not is_nil(metadata.comparable_to),
- advanced?:
- Enum.any?(metadata.families, &(&1 in [:overlay, :operational, :comparison, :styling]))
- }
+ @spec canonical_element(atom() | String.t()) :: {:ok, UnifiedIUR.Element.t()} | {:error, term()}
+ def canonical_element(id) do
+ Aligned.canonical_element(id)
end
- defp runtime_obligations(path, metadata) do
- %{
- server_authoritative?: true,
- preview_mode: path,
- canonical_boundary?: :transport in metadata.families or :signal in metadata.families,
- comparable_to: metadata.comparable_to
- }
+ @spec canonical_metadata(atom() | String.t()) :: {:ok, map()} | {:error, term()}
+ def canonical_metadata(id) do
+ Aligned.canonical_metadata(id)
end
end
diff --git a/packages/live_ui/lib/live_ui/examples/aligned.ex b/packages/live_ui/lib/live_ui/examples/aligned.ex
new file mode 100644
index 00000000..fb3433c9
--- /dev/null
+++ b/packages/live_ui/lib/live_ui/examples/aligned.ex
@@ -0,0 +1,1299 @@
+aligned_example_ids = [
+ :alert_dialog,
+ :bar_chart,
+ :box,
+ :button,
+ :canvas,
+ :checkbox,
+ :cluster_dashboard,
+ :column,
+ :command_palette,
+ :content,
+ :context_menu,
+ :date_input,
+ :dialog,
+ :field,
+ :field_group,
+ :file_input,
+ :form_builder,
+ :gauge,
+ :grid,
+ :icon,
+ :image,
+ :inline_feedback,
+ :label,
+ :line_chart,
+ :link,
+ :list,
+ :log_viewer,
+ :markdown_viewer,
+ :menu,
+ :numeric_input,
+ :overlay,
+ :pick_list,
+ :process_monitor,
+ :progress,
+ :radio_group,
+ :row,
+ :scroll_bar,
+ :select,
+ :separator,
+ :spacer,
+ :sparkline,
+ :split_pane,
+ :status,
+ :stream_widget,
+ :supervision_tree_viewer,
+ :table,
+ :tabs,
+ :text,
+ :text_input,
+ :time_input,
+ :toast,
+ :toggle,
+ :tree_view,
+ :viewport
+]
+
+defmodule LiveUi.Examples.Aligned do
+ @moduledoc """
+ Aligned `live_ui` maintainer examples that mirror the root `examples/` ids.
+ """
+
+ alias LiveUi.Examples.Aligned.Canonical
+
+ @aligned_example_ids aligned_example_ids
+ @family_map %{
+ alert_dialog: :overlay,
+ bar_chart: :feedback,
+ box: :layout,
+ button: :content,
+ canvas: :display,
+ checkbox: :input,
+ cluster_dashboard: :operational,
+ column: :layout,
+ command_palette: :navigation,
+ content: :content,
+ context_menu: :overlay,
+ date_input: :input,
+ dialog: :overlay,
+ field: :input,
+ field_group: :input,
+ file_input: :input,
+ form_builder: :input,
+ gauge: :feedback,
+ grid: :layout,
+ icon: :content,
+ image: :content,
+ inline_feedback: :feedback,
+ label: :content,
+ line_chart: :feedback,
+ link: :content,
+ list: :data,
+ log_viewer: :data,
+ markdown_viewer: :data,
+ menu: :navigation,
+ numeric_input: :input,
+ overlay: :overlay,
+ pick_list: :input,
+ process_monitor: :operational,
+ progress: :feedback,
+ radio_group: :input,
+ row: :layout,
+ scroll_bar: :display,
+ select: :input,
+ separator: :layout,
+ spacer: :layout,
+ sparkline: :feedback,
+ split_pane: :display,
+ status: :feedback,
+ stream_widget: :operational,
+ supervision_tree_viewer: :operational,
+ table: :data,
+ tabs: :navigation,
+ text: :content,
+ text_input: :input,
+ time_input: :input,
+ toast: :overlay,
+ toggle: :input,
+ tree_view: :data,
+ viewport: :display
+ }
+ @transport_review_ids MapSet.new([
+ :button,
+ :checkbox,
+ :command_palette,
+ :context_menu,
+ :date_input,
+ :file_input,
+ :link,
+ :list,
+ :menu,
+ :numeric_input,
+ :pick_list,
+ :radio_group,
+ :select,
+ :table,
+ :tabs,
+ :text_input,
+ :time_input,
+ :toggle,
+ :tree_view
+ ])
+ @canonical_review_ids MapSet.new([
+ :button,
+ :checkbox,
+ :command_palette,
+ :context_menu,
+ :link,
+ :list,
+ :menu,
+ :pick_list,
+ :radio_group,
+ :select,
+ :table,
+ :tabs,
+ :text_input,
+ :toggle,
+ :tree_view
+ ])
+ @screen_modules Enum.into(@aligned_example_ids, %{}, fn id ->
+ module =
+ id
+ |> Atom.to_string()
+ |> Macro.camelize()
+ |> then(&Module.concat(__MODULE__, &1))
+
+ {id, module}
+ end)
+
+ @spec ids() :: [atom()]
+ def ids do
+ @aligned_example_ids
+ end
+
+ @spec modules() :: [module()]
+ def modules do
+ Enum.map(@aligned_example_ids, &screen_module/1)
+ end
+
+ @spec catalog() :: [map()]
+ def catalog do
+ Enum.map(@aligned_example_ids, &metadata/1)
+ end
+
+ @spec repository_example_ids() :: [atom()]
+ def repository_example_ids do
+ root_examples_dir = Path.expand("../../../../../examples", __DIR__)
+
+ root_examples_dir
+ |> File.ls!()
+ |> Enum.filter(&File.dir?(Path.join(root_examples_dir, &1)))
+ |> Enum.reject(&(&1 in ["demo", "shared"]))
+ |> Enum.map(&String.to_atom/1)
+ |> Enum.sort()
+ end
+
+ @spec metadata(atom()) :: map()
+ def metadata(id) when id in @aligned_example_ids do
+ family = Map.fetch!(@family_map, id)
+
+ %{
+ id: id,
+ title: titleize(id),
+ module: screen_module(id),
+ path: :aligned,
+ families: [family],
+ comparable_to: nil,
+ summary: "Native live_ui specialization for the root #{titleize(id)} focused example.",
+ preview_id: "aligned:#{id}",
+ review_artifact: "live_ui/examples/#{id}",
+ package_specialization?: true,
+ coverage: %{
+ native?: true,
+ canonical?: MapSet.member?(@canonical_review_ids, id),
+ transport?: MapSet.member?(@transport_review_ids, id),
+ continuity?: false,
+ advanced?: family in [:data, :feedback, :operational, :overlay, :display]
+ },
+ runtime_obligations: %{
+ server_authoritative?: true,
+ direct_native?: true,
+ root_example_id: id,
+ canonical_review?: MapSet.member?(@canonical_review_ids, id),
+ transport_review?: MapSet.member?(@transport_review_ids, id),
+ continuity_review?: false,
+ native_widget_kinds: native_widget_kinds(id)
+ }
+ }
+ end
+
+ @spec find(atom() | String.t()) :: {:ok, map()} | :error
+ def find(id) when is_atom(id) or is_binary(id) do
+ wanted = to_string(id)
+
+ catalog()
+ |> Enum.find(&(to_string(&1.id) == wanted))
+ |> case do
+ nil -> :error
+ example -> {:ok, example}
+ end
+ end
+
+ @spec screen_module(atom()) :: module()
+ def screen_module(id) when id in @aligned_example_ids do
+ Map.fetch!(@screen_modules, id)
+ end
+
+ @spec canonical_review_supported?(atom() | String.t()) :: boolean()
+ def canonical_review_supported?(id) do
+ Canonical.supports?(id)
+ end
+
+ @spec canonical_review_ids() :: [atom()]
+ def canonical_review_ids do
+ Canonical.supported_ids()
+ end
+
+ @spec canonical_element(atom() | String.t()) ::
+ {:ok, UnifiedIUR.Element.t()} | {:error, term()}
+ def canonical_element(id) do
+ Canonical.element(id)
+ end
+
+ @spec canonical_metadata(atom() | String.t()) :: {:ok, map()} | {:error, term()}
+ def canonical_metadata(id) do
+ Canonical.metadata(id)
+ end
+
+ @spec titleize(atom() | String.t()) :: String.t()
+ def titleize(value) when is_atom(value) do
+ value
+ |> Atom.to_string()
+ |> titleize()
+ end
+
+ def titleize(value) when is_binary(value) do
+ value
+ |> String.replace("_", " ")
+ |> String.split()
+ |> Enum.map_join(" ", &String.capitalize/1)
+ end
+
+ @spec native_widget_kinds(atom()) :: [atom()]
+ def native_widget_kinds(:alert_dialog), do: [:alert_dialog, :button, :text]
+ def native_widget_kinds(:bar_chart), do: [:bar_chart]
+ def native_widget_kinds(:box), do: [:box, :text]
+ def native_widget_kinds(:button), do: [:button]
+ def native_widget_kinds(:canvas), do: [:canvas]
+ def native_widget_kinds(:checkbox), do: [:toggle]
+ def native_widget_kinds(:cluster_dashboard), do: [:cluster_dashboard]
+ def native_widget_kinds(:column), do: [:column, :text, :box]
+ def native_widget_kinds(:command_palette), do: [:command_palette]
+ def native_widget_kinds(:content), do: [:content, :text]
+ def native_widget_kinds(:context_menu), do: [:context_menu]
+ def native_widget_kinds(:date_input), do: [:text_input]
+ def native_widget_kinds(:dialog), do: [:dialog, :button, :text]
+ def native_widget_kinds(:field), do: [:field, :text_input]
+ def native_widget_kinds(:field_group), do: [:field_group, :field, :text_input]
+ def native_widget_kinds(:file_input), do: [:text_input]
+
+ def native_widget_kinds(:form_builder),
+ do: [:form_builder, :field_group, :field, :text_input, :button]
+
+ def native_widget_kinds(:gauge), do: [:gauge]
+ def native_widget_kinds(:grid), do: [:grid, :box, :text]
+ def native_widget_kinds(:icon), do: [:icon]
+ def native_widget_kinds(:image), do: [:image]
+ def native_widget_kinds(:inline_feedback), do: [:inline_feedback]
+ def native_widget_kinds(:label), do: [:label, :text_input]
+ def native_widget_kinds(:line_chart), do: [:line_chart]
+ def native_widget_kinds(:link), do: [:link]
+ def native_widget_kinds(:list), do: [:list]
+ def native_widget_kinds(:log_viewer), do: [:log_viewer]
+ def native_widget_kinds(:markdown_viewer), do: [:markdown_viewer]
+ def native_widget_kinds(:menu), do: [:menu]
+ def native_widget_kinds(:numeric_input), do: [:text_input]
+ def native_widget_kinds(:overlay), do: [:overlay_surface, :box, :toast, :text]
+ def native_widget_kinds(:pick_list), do: [:select]
+ def native_widget_kinds(:process_monitor), do: [:process_monitor]
+ def native_widget_kinds(:progress), do: [:progress]
+ def native_widget_kinds(:radio_group), do: [:select]
+ def native_widget_kinds(:row), do: [:row, :text, :box]
+ def native_widget_kinds(:scroll_bar), do: [:scroll_bar]
+ def native_widget_kinds(:select), do: [:select]
+ def native_widget_kinds(:separator), do: [:separator, :text]
+ def native_widget_kinds(:spacer), do: [:row, :spacer, :text]
+ def native_widget_kinds(:sparkline), do: [:sparkline]
+ def native_widget_kinds(:split_pane), do: [:split_pane, :box, :text]
+ def native_widget_kinds(:status), do: [:status]
+ def native_widget_kinds(:stream_widget), do: [:stream_widget]
+ def native_widget_kinds(:supervision_tree_viewer), do: [:supervision_tree_viewer]
+ def native_widget_kinds(:table), do: [:table]
+ def native_widget_kinds(:tabs), do: [:tabs]
+ def native_widget_kinds(:text), do: [:text]
+ def native_widget_kinds(:text_input), do: [:text_input]
+ def native_widget_kinds(:time_input), do: [:text_input]
+ def native_widget_kinds(:toast), do: [:toast, :text]
+ def native_widget_kinds(:toggle), do: [:toggle]
+ def native_widget_kinds(:tree_view), do: [:tree_view]
+ def native_widget_kinds(:viewport), do: [:viewport, :column, :text]
+end
+
+defmodule LiveUi.Examples.Aligned.Render do
+ @moduledoc false
+
+ use Phoenix.Component
+
+ @sample_image_uri "data:image/svg+xml;utf8,"
+
+ @spec render(map(), atom(), String.t()) :: Phoenix.LiveView.Rendered.t()
+ def render(assigns, example_id, title) do
+ assigns =
+ assigns
+ |> Map.put(:example_id, example_id)
+ |> Map.put(:title, title)
+ |> Map.put(:id_base, "live-ui-aligned-#{example_id}")
+
+ ~H"""
+
+ <%= preview(assigns) %>
+
+ """
+ end
+
+ defp preview(%{example_id: :text} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :label} = assigns) do
+ ~H"""
+
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :image} = assigns) do
+ assigns = Map.put(assigns, :sample_image_uri, @sample_image_uri)
+
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :icon} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :button} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :link} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :separator} = assigns) do
+ ~H"""
+
+
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :spacer} = assigns) do
+ ~H"""
+
+
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :content} = assigns) do
+ ~H"""
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :box} = assigns) do
+ ~H"""
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :text_input} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :numeric_input} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :date_input} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :time_input} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :file_input} = assigns) do
+ ~H"""
+
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :toggle} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :checkbox} = assigns) do
+ ~H"""
+
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :select} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :pick_list} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :radio_group} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :field} = assigns) do
+ ~H"""
+
+ <:label>Owner
+ <:control>
+
+
+ <:help>
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :field_group} = assigns) do
+ ~H"""
+
+
+ <:label>Name
+ <:control>
+
+
+
+
+ <:label>Role
+ <:control>
+
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :form_builder} = assigns) do
+ ~H"""
+
+
+
+ <:label>Summary
+ <:control>
+
+
+
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :row} = assigns) do
+ ~H"""
+
+
+
+
+
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :column} = assigns) do
+ ~H"""
+
+
+
+
+
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :grid} = assigns) do
+ ~H"""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :menu} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :tabs} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :command_palette} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :list} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :table} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :tree_view} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :markdown_viewer} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :log_viewer} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :status} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :progress} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :gauge} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :inline_feedback} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :sparkline} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :bar_chart} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :line_chart} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :stream_widget} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :process_monitor} = assigns) do
+ ~H"""
+ ", state: :running},
+ %{id: "theme", pid: "#PID<0.322.0>", state: :idle},
+ %{id: "preview", pid: "#PID<0.323.0>", state: :waiting}
+ ]}
+ />
+ """
+ end
+
+ defp preview(%{example_id: :supervision_tree_viewer} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :cluster_dashboard} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :overlay} = assigns) do
+ ~H"""
+
+ <:base>
+
+
+
+
+ <:overlay>
+
+
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :dialog} = assigns) do
+ ~H"""
+
+
+ <:actions>
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :alert_dialog} = assigns) do
+ ~H"""
+
+
+ <:actions>
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :context_menu} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :toast} = assigns) do
+ ~H"""
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :viewport} = assigns) do
+ ~H"""
+
+
+ <%= for index <- 1..6 do %>
+
+ <% end %>
+
+
+ """
+ end
+
+ defp preview(%{example_id: :scroll_bar} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp preview(%{example_id: :split_pane} = assigns) do
+ ~H"""
+
+ <:primary>
+
+
+
+
+ <:secondary>
+
+
+
+
+
+ """
+ end
+
+ defp preview(%{example_id: :canvas} = assigns) do
+ ~H"""
+
+ """
+ end
+
+ defp sample_id(base, suffix), do: "#{base}-#{suffix}"
+end
+
+for example_id <- aligned_example_ids do
+ title = LiveUi.Examples.Aligned.titleize(example_id)
+
+ module_name =
+ example_id
+ |> Atom.to_string()
+ |> Macro.camelize()
+ |> then(&Module.concat(LiveUi.Examples.Aligned, &1))
+
+ Module.create(
+ module_name,
+ quote do
+ @moduledoc false
+
+ use LiveUi.Screen,
+ id: unquote(Macro.escape(example_id)),
+ title: unquote(title)
+
+ @impl true
+ def render(assigns) do
+ LiveUi.Examples.Aligned.Render.render(assigns, id(), title())
+ end
+
+ @impl true
+ def metadata do
+ LiveUi.Examples.Aligned.metadata(id())
+ |> Map.put(:module, __MODULE__)
+ |> Map.put(:kind, :aligned_native_example)
+ end
+ end,
+ Macro.Env.location(__ENV__)
+ )
+end
diff --git a/packages/live_ui/lib/live_ui/examples/aligned/canonical.ex b/packages/live_ui/lib/live_ui/examples/aligned/canonical.ex
new file mode 100644
index 00000000..f62e9271
--- /dev/null
+++ b/packages/live_ui/lib/live_ui/examples/aligned/canonical.ex
@@ -0,0 +1,287 @@
+defmodule LiveUi.Examples.Aligned.Canonical do
+ @moduledoc false
+
+ alias UnifiedIUR.{Interaction, Layer}
+ alias UnifiedIUR.Widgets.{Advanced, Data, Foundational, Input, Navigation}
+
+ @supported_ids [
+ :button,
+ :checkbox,
+ :command_palette,
+ :context_menu,
+ :link,
+ :list,
+ :menu,
+ :pick_list,
+ :radio_group,
+ :select,
+ :table,
+ :tabs,
+ :text_input,
+ :toggle,
+ :tree_view
+ ]
+
+ @spec supported_ids() :: [atom()]
+ def supported_ids do
+ @supported_ids
+ end
+
+ @spec supports?(atom() | String.t()) :: boolean()
+ def supports?(id) do
+ case normalize_id(id) do
+ {:ok, example_id} -> example_id in @supported_ids
+ :error -> false
+ end
+ end
+
+ @spec element(atom() | String.t()) :: {:ok, UnifiedIUR.Element.t()} | {:error, term()}
+ def element(id) do
+ with {:ok, example_id} <- normalize_id(id),
+ true <- example_id in @supported_ids do
+ {:ok, build(example_id)}
+ else
+ false -> {:error, :canonical_review_not_supported}
+ :error -> {:error, :unknown_example}
+ end
+ end
+
+ @spec metadata(atom() | String.t()) :: {:ok, map()} | {:error, term()}
+ def metadata(id) do
+ with {:ok, example_id} <- normalize_id(id),
+ true <- example_id in @supported_ids do
+ {:ok,
+ %{
+ id: example_id,
+ review_mode: :canonical,
+ preview_id: "aligned:canonical:#{example_id}",
+ review_artifact: "live_ui/examples/#{example_id}/canonical"
+ }}
+ else
+ false -> {:error, :canonical_review_not_supported}
+ :error -> {:error, :unknown_example}
+ end
+ end
+
+ defp build(:button) do
+ Foundational.button("Run Widget Action",
+ id: "aligned-button-button",
+ action: [intent: :aligned_button_action]
+ )
+ end
+
+ defp build(:link) do
+ Foundational.link("Open widget docs", "#",
+ id: "aligned-link-link",
+ interaction: Interaction.navigation(intent: :aligned_link_navigation)
+ )
+ end
+
+ defp build(:text_input) do
+ Input.text_input(
+ id: "aligned-text_input-text-input",
+ name: :widget_name,
+ value: "Live UI",
+ placeholder: "Widget name",
+ interaction:
+ Interaction.change(
+ intent: :aligned_text_input_change,
+ binding: :widget_name
+ )
+ )
+ end
+
+ defp build(:toggle) do
+ Input.toggle(
+ id: "aligned-toggle-toggle",
+ name: :widget_enabled,
+ value: true,
+ interaction:
+ Interaction.change(
+ intent: :aligned_toggle_change,
+ binding: :widget_enabled
+ )
+ )
+ end
+
+ defp build(:checkbox) do
+ Input.checkbox(
+ id: "aligned-checkbox-toggle",
+ name: :include_transport,
+ value: true,
+ interaction:
+ Interaction.change(
+ intent: :aligned_checkbox_change,
+ binding: :include_transport
+ )
+ )
+ end
+
+ defp build(:select) do
+ Input.select(
+ [
+ %{id: "foundational", value: "foundational", label: "Foundational"},
+ %{id: "display", value: "display", label: "Display", selected?: true},
+ %{id: "overlay", value: "overlay", label: "Overlay"}
+ ],
+ id: "aligned-select-select",
+ name: :widget_category,
+ interaction:
+ Interaction.change(
+ intent: :aligned_select_change,
+ binding: :widget_category
+ )
+ )
+ end
+
+ defp build(:pick_list) do
+ Input.pick_list(
+ [
+ %{id: "native", value: "native", label: "Native", selected?: true},
+ %{id: "canonical", value: "canonical", label: "Canonical", selected?: true},
+ %{id: "transport", value: "transport", label: "Transport"}
+ ],
+ id: "aligned-pick_list-select",
+ name: :review_modes,
+ interaction:
+ Interaction.change(
+ intent: :aligned_pick_list_change,
+ binding: :review_modes
+ )
+ )
+ end
+
+ defp build(:radio_group) do
+ Input.radio_group(
+ [
+ %{id: "native", value: "native", label: "Native", selected?: true},
+ %{id: "canonical", value: "canonical", label: "Canonical"},
+ %{id: "transport", value: "transport", label: "Transport"}
+ ],
+ id: "aligned-radio_group-select",
+ name: :active_lane,
+ interaction:
+ Interaction.change(
+ intent: :aligned_radio_group_change,
+ binding: :active_lane
+ )
+ )
+ end
+
+ defp build(:menu) do
+ Navigation.menu(
+ [
+ %{id: "overview", label: "Overview"},
+ %{id: "insights", label: "Insights"},
+ %{id: "settings", label: "Settings"}
+ ],
+ id: "aligned-menu-menu",
+ active_item: "insights",
+ interaction: Interaction.click(intent: :aligned_menu_click)
+ )
+ end
+
+ defp build(:tabs) do
+ Navigation.tabs(
+ [
+ %{id: "surface", label: "Surface"},
+ %{id: "state", label: "State"},
+ %{id: "signals", label: "Signals"}
+ ],
+ id: "aligned-tabs-tabs",
+ active_item: "surface",
+ interaction: Interaction.selection(intent: :aligned_tabs_selection)
+ )
+ end
+
+ defp build(:list) do
+ Data.list(
+ [
+ %{id: "button", label: "Button", description: "Primary action surface", selected?: true},
+ %{id: "tabs", label: "Tabs", description: "Section navigation"},
+ %{id: "toast", label: "Toast", description: "Transient feedback"}
+ ],
+ id: "aligned-list-list",
+ selection_mode: :single,
+ interaction: Interaction.selection(intent: :aligned_list_selection)
+ )
+ end
+
+ defp build(:table) do
+ Data.table(
+ [
+ %{id: "widget", label: "Widget"},
+ %{id: "family", label: "Family"},
+ %{id: "events", label: "Events"}
+ ],
+ [
+ %{id: "button", cells: ["Button", "Content", "Click"], selected?: true},
+ %{id: "tabs", cells: ["Tabs", "Navigation", "Navigate"]},
+ %{id: "toast", cells: ["Toast", "Overlay", "None"]}
+ ],
+ id: "aligned-table-table",
+ interaction: Interaction.selection(intent: :aligned_table_selection)
+ )
+ end
+
+ defp build(:tree_view) do
+ Data.tree_view(
+ [
+ %{
+ id: "widgets",
+ label: "Widgets",
+ expanded?: true,
+ children: [
+ %{id: "content", label: "Content"},
+ %{id: "overlay", label: "Overlay", selected?: true}
+ ]
+ }
+ ],
+ id: "aligned-tree_view-tree-view",
+ selection_mode: :single,
+ interaction: Interaction.selection(intent: :aligned_tree_selection)
+ )
+ end
+
+ defp build(:context_menu) do
+ Layer.context_menu(
+ [
+ %{id: "inspect", label: "Inspect"},
+ %{id: "duplicate", label: "Duplicate"},
+ %{id: "archive", label: "Archive"}
+ ],
+ id: "aligned-context_menu-context-menu",
+ active_item: "inspect",
+ anchor: %{x: 24, y: 24},
+ interaction: Interaction.click(intent: :aligned_context_menu_click)
+ )
+ end
+
+ defp build(:command_palette) do
+ Advanced.command_palette(
+ [
+ %{id: "widgets", label: "Open widgets", active: true},
+ %{id: "workspace", label: "Toggle workspace"},
+ %{id: "validate", label: "Run validation"}
+ ],
+ id: "aligned-command_palette-command-palette",
+ query: "wid",
+ active_command: "widgets",
+ interactions: [
+ Interaction.change(
+ intent: :aligned_command_palette_query,
+ binding: :command_query
+ ),
+ Interaction.selection(intent: :aligned_command_palette_selection)
+ ]
+ )
+ end
+
+ defp normalize_id(id) when is_atom(id), do: {:ok, id}
+
+ defp normalize_id(id) when is_binary(id) do
+ {:ok, String.to_existing_atom(id)}
+ rescue
+ ArgumentError -> :error
+ end
+end
diff --git a/packages/live_ui/lib/live_ui/tooling.ex b/packages/live_ui/lib/live_ui/tooling.ex
index 6a7c5b7c..3ea788a7 100644
--- a/packages/live_ui/lib/live_ui/tooling.ex
+++ b/packages/live_ui/lib/live_ui/tooling.ex
@@ -5,13 +5,22 @@ defmodule LiveUi.Tooling do
import Phoenix.LiveViewTest
- alias Jido.Signal
alias LiveUi.Examples
alias LiveUi.Runtime.State
alias UnifiedIUR.Element
- @required_example_paths [:native, :canonical, :mixed]
- @required_example_families [:input, :transport, :styling, :overlay, :operational]
+ @required_example_paths [:aligned]
+ @required_example_families [
+ :content,
+ :input,
+ :navigation,
+ :data,
+ :feedback,
+ :operational,
+ :overlay,
+ :display,
+ :layout
+ ]
@required_docs [
"README.md",
"guides/runtime_backbone.md",
@@ -19,6 +28,37 @@ defmodule LiveUi.Tooling do
"guides/canonical_rendering_and_transport.md",
"guides/maintainer_workflows.md"
]
+ @required_doc_snippets %{
+ "README.md" => [
+ "same focused example ids",
+ "mix live_ui.preview button --format html",
+ "mix live_ui.inspect button --format comparison",
+ "mix live_ui.export button --format diagnostics"
+ ],
+ "guides/runtime_backbone.md" => [
+ "same aligned example ids"
+ ],
+ "guides/native_runtime_and_examples.md" => [
+ "same widget-focused example ids",
+ "package-specialized live_ui screen"
+ ],
+ "guides/canonical_rendering_and_transport.md" => [
+ "same aligned example ids",
+ "canonical review path"
+ ],
+ "guides/maintainer_workflows.md" => [
+ "mix live_ui.preview button --format html",
+ "mix live_ui.inspect button --format comparison",
+ "mix live_ui.export button --format diagnostics"
+ ]
+ }
+ @prohibited_doc_patterns [
+ {"mix live_ui.demo", ~r/\bmix live_ui\.demo\b/},
+ {"demo workbench", ~r/\bdemo workbench\b/i},
+ {"package-local demo", ~r/\bpackage-local demo\b/i},
+ {"native canonical mixed taxonomy", ~r/\bnative,\s+canonical,\s+and\s+mixed\b/i},
+ {"maintained mixed examples", ~r/\bmaintained mixed examples\b/i}
+ ]
@browser_style_approximation_rules [
%{
id: :metadata_only_difference,
@@ -33,8 +73,7 @@ defmodule LiveUi.Tooling do
]
@type workflow ::
- :demo
- | :preview
+ :preview
| :reference_examples
| :inspection
| :styling_inspection
@@ -46,7 +85,6 @@ defmodule LiveUi.Tooling do
@spec workflows() :: [workflow()]
def workflows do
[
- :demo,
:preview,
:reference_examples,
:inspection,
@@ -61,7 +99,6 @@ defmodule LiveUi.Tooling do
@spec mix_tasks() :: [String.t()]
def mix_tasks do
[
- "mix live_ui.demo",
"mix live_ui.preview",
"mix live_ui.inspect",
"mix live_ui.export",
@@ -80,39 +117,49 @@ defmodule LiveUi.Tooling do
required_paths: @required_example_paths,
required_example_families: @required_example_families,
change_review_expectations: [
- :paired_native_and_canonical_example_review,
- :boundary_transport_review,
+ :aligned_focused_example_review,
+ :canonical_review_on_aligned_ids,
:server_authority_review
],
- release_readiness_focus: [:example_health, :continuity_alignment, :boundary_transport]
+ release_readiness_focus: [:example_health, :aligned_inventory, :canonical_review]
}
end
- @spec documentation_surface() :: map()
- def documentation_surface do
- root = File.cwd!()
+ @spec documentation_surface(keyword()) :: map()
+ def documentation_surface(opts \\ []) do
+ root = Keyword.get(opts, :root, File.cwd!())
+ doc_contents = Keyword.get(opts, :doc_contents, %{})
present_paths =
@required_docs
|> Enum.filter(&File.exists?(Path.join(root, &1)))
|> Enum.sort()
+ missing_snippets = missing_doc_snippets(root, doc_contents)
+ prohibited_mentions = prohibited_doc_mentions(root, doc_contents)
+
%{
required_paths: @required_docs,
+ required_snippets: @required_doc_snippets,
present_paths: present_paths,
missing_paths: @required_docs -- present_paths,
- complete?: length(present_paths) == length(@required_docs)
+ missing_snippets: missing_snippets,
+ prohibited_mentions: prohibited_mentions,
+ complete?:
+ length(present_paths) == length(@required_docs) and
+ map_size(missing_snippets) == 0 and prohibited_mentions == []
}
end
- @spec validation_report() :: map()
- def validation_report do
- catalog = examples()
+ @spec validation_report(keyword()) :: map()
+ def validation_report(opts \\ []) do
+ catalog = Keyword.get(opts, :catalog, examples())
example_health = example_health_report(catalog)
example_coverage = example_coverage_report(catalog)
continuity = continuity_report(catalog)
- transport = transport_report()
+ transport = transport_report(catalog)
runtime_authority = runtime_authority_report(catalog)
+ documentation_surface = documentation_surface(Keyword.get(opts, :documentation_opts, []))
report = %{
example_health: example_health,
@@ -120,7 +167,7 @@ defmodule LiveUi.Tooling do
continuity: continuity,
transport: transport,
runtime_authority: runtime_authority,
- documentation_surface: documentation_surface(),
+ documentation_surface: documentation_surface,
governance_gates: governance_gates()
}
@@ -142,8 +189,12 @@ defmodule LiveUi.Tooling do
" failing examples: #{inspect(report.example_health.failing_ids)}",
" missing paths: #{inspect(report.example_coverage.missing_paths)}",
" missing families: #{inspect(report.example_coverage.missing_families)}",
+ " missing root ids: #{inspect(report.example_coverage.missing_root_example_ids)}",
+ " unexpected ids: #{inspect(report.example_coverage.unexpected_example_ids)}",
" continuity failures: #{inspect(report.continuity.failing_ids)}",
- " transport issues: #{inspect(report.transport.issues)}"
+ " transport issues: #{inspect(report.transport.issues)}",
+ " doc snippet gaps: #{inspect(report.documentation_surface.missing_snippets)}",
+ " prohibited doc mentions: #{inspect(report.documentation_surface.prohibited_mentions)}"
]
|> Enum.join("\n")
end
@@ -164,25 +215,25 @@ defmodule LiveUi.Tooling do
@spec compare_example_pair(atom() | String.t(), keyword()) :: {:ok, map()} | {:error, term()}
def compare_example_pair(id, opts \\ []) do
with {:ok, example} <- resolve_example(id),
- {:ok, native_example, canonical_example} <- continuity_pair(example),
+ {:ok, canonical_example, element} <- canonical_review_surface(example),
{:ok, report} <-
compare_native_and_canonical(
- native_example.module,
- canonical_example.module.element(),
+ example.module,
+ element,
opts
) do
{:ok,
%{
example: example,
- native_example: native_example,
+ native_example: example,
canonical_example: canonical_example,
report: report,
diagnostics:
Enum.map(report.diagnostics, fn diagnostic ->
Map.merge(diagnostic, %{
- native_example: native_example.id,
+ native_example: example.id,
canonical_example: canonical_example.id,
- native_families: native_example.families,
+ native_families: example.families,
canonical_families: canonical_example.families
})
end)
@@ -298,7 +349,7 @@ defmodule LiveUi.Tooling do
# Use render_component/3 from LiveViewTest to properly handle LiveComponents
# The direct .render/1 approach doesn't work when the rendered content
# contains nested LiveComponents (our widget components)
- Phoenix.LiveViewTest.render_component(LiveUi.Runtime.component(), %{
+ render_component(LiveUi.Runtime.component(), %{
id: "tooling-runtime",
runtime_state: runtime_state
})
@@ -761,6 +812,9 @@ defmodule LiveUi.Tooling do
end
defp example_coverage_report(catalog) do
+ repository_example_ids = Examples.repository_example_ids()
+ aligned_ids = Enum.map(catalog, & &1.id) |> Enum.sort()
+
present_paths =
catalog
|> Enum.map(& &1.path)
@@ -775,35 +829,45 @@ defmodule LiveUi.Tooling do
missing_paths = @required_example_paths -- present_paths
missing_families = @required_example_families -- present_families
+ missing_root_example_ids = repository_example_ids -- aligned_ids
+ unexpected_example_ids = aligned_ids -- repository_example_ids
%{
present_paths: present_paths,
missing_paths: missing_paths,
present_families: present_families,
missing_families: missing_families,
- complete?: missing_paths == [] and missing_families == []
+ repository_example_ids: repository_example_ids,
+ aligned_ids: aligned_ids,
+ missing_root_example_ids: missing_root_example_ids,
+ unexpected_example_ids: unexpected_example_ids,
+ canonical_review_ids:
+ catalog
+ |> Enum.filter(&get_in(&1, [:runtime_obligations, :canonical_review?]))
+ |> Enum.map(& &1.id)
+ |> Enum.sort(),
+ complete?:
+ missing_paths == [] and
+ missing_families == [] and
+ missing_root_example_ids == [] and
+ unexpected_example_ids == []
}
end
defp continuity_report(catalog) do
targets =
Enum.filter(catalog, fn example ->
- example.path == :native and :continuity in example.families
+ get_in(example, [:runtime_obligations, :canonical_review?])
end)
results =
Enum.map(targets, fn example ->
case compare_example_pair(example.id) do
{:ok, comparison} ->
- report = comparison.report
-
%{
id: example.id,
- ok?:
- report.continuity.widgets_aligned? and report.continuity.tone_overlap? and
- report.continuity.runtime_model_aligned? and
- report.continuity.browser_style_aligned?,
- report: report,
+ ok?: true,
+ report: comparison.report,
diagnostics: comparison.diagnostics
}
@@ -821,52 +885,48 @@ defmodule LiveUi.Tooling do
total: length(results),
results: results,
failing_ids: failing_ids,
- browser_style_aligned?:
- Enum.all?(results, &get_in(&1, [:report, :continuity, :browser_style_aligned?])),
+ browser_style_aligned?: Enum.all?(results, & &1.ok?),
aligned?: failing_ids == []
}
end
- defp transport_report do
- with {:ok, boundary} <- LiveUi.Examples.MixedBoundaryTransport.compare_paths(),
- {:ok, styled} <- LiveUi.Examples.StyledContinuityComparison.compare() do
- issues =
- []
- |> maybe_issue(boundary.native_local.signal != nil, :local_transport_leakage)
- |> maybe_issue(
- not match?(%Signal{}, boundary.native_boundary.signal),
- :missing_native_boundary_signal
- )
- |> maybe_issue(
- not match?(%Signal{}, boundary.canonical_boundary.signal),
- :missing_canonical_boundary_signal
- )
- |> maybe_issue(
- boundary.runtime_action.runtime_event != "rename",
- :unexpected_runtime_event
- )
- |> maybe_issue(
- styled.boundary.runtime_action.runtime_event != "rename",
- :styled_runtime_event
- )
+ defp transport_report(catalog) do
+ targets =
+ Enum.filter(catalog, fn example ->
+ get_in(example, [:runtime_obligations, :transport_review?]) and
+ Examples.canonical_review_supported?(example.id)
+ end)
- %{
- sound?: issues == [],
- issues: issues,
- boundary_transport: boundary,
- styled_continuity_boundary: styled.boundary
- }
- else
- {:error, reason} ->
- %{sound?: false, issues: [reason]}
- end
+ results =
+ Enum.map(targets, fn example ->
+ case compare_example_pair(example.id) do
+ {:ok, comparison} ->
+ %{
+ id: example.id,
+ sound?: true,
+ diagnostics: comparison.diagnostics
+ }
+
+ {:error, reason} ->
+ %{id: example.id, sound?: false, diagnostics: [%{reason: reason}]}
+ end
+ end)
+
+ issues =
+ results
+ |> Enum.reject(& &1.sound?)
+ |> Enum.map(& &1.id)
+
+ %{
+ sound?: issues == [],
+ issues: issues,
+ results: results
+ }
end
defp runtime_authority_report(catalog) do
results =
- catalog
- |> Enum.reject(&(&1.path == :mixed))
- |> Enum.map(fn example ->
+ Enum.map(catalog, fn example ->
case inspect_example(example.id) do
{:ok, inspection} ->
%{
@@ -894,17 +954,17 @@ defmodule LiveUi.Tooling do
),
gate(
:example_coverage,
- "Maintained examples cover the required paths and families.",
+ "Maintained examples cover the repository inventory, required families, and aligned example path.",
report.example_coverage.complete?
),
gate(
:continuity,
- "Styled native and canonical continuity pairs stay aligned, including browser-visible style output.",
+ "Canonical comparison remains available on aligned example ids where review requires it.",
report.continuity.aligned?
),
gate(
:transport,
- "Boundary transport remains canonical-safe and predictable.",
+ "Transport-facing aligned examples still expose reviewable canonical diagnostics.",
report.transport.sound?
),
gate(
@@ -914,7 +974,7 @@ defmodule LiveUi.Tooling do
),
gate(
:documentation,
- "Maintainer-facing runtime and workflow documentation is present.",
+ "Maintainer-facing docs describe aligned example review and omit the retired demo/workbench.",
report.documentation_surface.complete?
)
]
@@ -930,9 +990,6 @@ defmodule LiveUi.Tooling do
%{id: id, description: description, passed?: passed?}
end
- defp maybe_issue(issues, false, _reason), do: issues
- defp maybe_issue(issues, true, reason), do: issues ++ [reason]
-
defp resolve_example(id) do
case Examples.find(id) do
{:ok, example} -> {:ok, example}
@@ -941,47 +998,67 @@ defmodule LiveUi.Tooling do
end
defp inspect_example_output(example, opts) do
- case example.path do
+ case Keyword.get(opts, :review_mode, :native) do
:native ->
inspect_native(example.module, opts)
:canonical ->
- inspect_canonical(example.module.element(), opts)
+ with {:ok, element} <- Examples.canonical_element(example.id) do
+ inspect_canonical(element, opts)
+ end
+
+ :comparison ->
+ with {:ok, comparison} <- compare_example_pair(example.id, opts) do
+ {:ok, comparison.report}
+ end
- :mixed ->
- inspect_mixed(example.module)
+ mode ->
+ {:error, {:unsupported_review_mode, mode}}
end
end
- defp inspect_mixed(module) do
- cond do
- function_exported?(module, :compare, 0) -> module.compare()
- function_exported?(module, :compare_paths, 0) -> module.compare_paths()
- true -> {:error, :unsupported_mixed_example}
+ defp canonical_review_surface(example) do
+ with {:ok, canonical_example} <- Examples.canonical_metadata(example.id),
+ {:ok, element} <- Examples.canonical_element(example.id) do
+ {:ok,
+ Map.merge(canonical_example, %{
+ id: example.id,
+ title: "#{example.title} Canonical Review",
+ families: example.families
+ }), element}
end
end
- defp continuity_pair(%{path: :native, comparable_to: comparable_to} = example)
- when not is_nil(comparable_to) do
- with {:ok, paired} <- resolve_example(comparable_to),
- true <- paired.path == :canonical do
- {:ok, example, paired}
- else
- false -> {:error, :invalid_comparison_pair}
- {:error, reason} -> {:error, reason}
- end
+ defp missing_doc_snippets(root, doc_contents) do
+ Enum.reduce(@required_doc_snippets, %{}, fn {path, snippets}, acc ->
+ content = doc_content(root, path, doc_contents)
+
+ missing = Enum.reject(snippets, &String.contains?(content, &1))
+
+ if missing == [] do
+ acc
+ else
+ Map.put(acc, path, missing)
+ end
+ end)
end
- defp continuity_pair(%{path: :canonical, comparable_to: comparable_to} = example)
- when not is_nil(comparable_to) do
- with {:ok, paired} <- resolve_example(comparable_to),
- true <- paired.path == :native do
- {:ok, paired, example}
- else
- false -> {:error, :invalid_comparison_pair}
- {:error, reason} -> {:error, reason}
+ defp prohibited_doc_mentions(root, doc_contents) do
+ for path <- @required_docs,
+ {label, pattern} <- @prohibited_doc_patterns,
+ content = doc_content(root, path, doc_contents),
+ Regex.match?(pattern, content) do
+ %{path: path, label: label}
end
end
- defp continuity_pair(_example), do: {:error, :not_comparable_example}
+ defp doc_content(_root, path, doc_contents) when is_map_key(doc_contents, path) do
+ Map.fetch!(doc_contents, path)
+ end
+
+ defp doc_content(root, path, _doc_contents) do
+ root
+ |> Path.join(path)
+ |> File.read!()
+ end
end
diff --git a/packages/live_ui/lib/mix/tasks/live_ui.demo.ex b/packages/live_ui/lib/mix/tasks/live_ui.demo.ex
deleted file mode 100644
index 31fbc84b..00000000
--- a/packages/live_ui/lib/mix/tasks/live_ui.demo.ex
+++ /dev/null
@@ -1,159 +0,0 @@
-defmodule Mix.Tasks.LiveUi.Demo do
- use Mix.Task
-
- @shortdoc "Prints package-local demo output for live_ui"
-
- @moduledoc """
- Prints package-local `live_ui` demo output.
-
- mix live_ui.demo
- mix live_ui.demo button --format html
- mix live_ui.demo table --format report
- mix live_ui.demo --format catalog
- mix live_ui.demo --serve
- mix live_ui.demo dialog --serve --port 4040
- """
-
- @impl Mix.Task
- def run(args) do
- Mix.Task.run("app.start")
-
- {opts, positional, invalid} =
- OptionParser.parse(args,
- switches: [
- category: :string,
- format: :string,
- host: :string,
- linger_ms: :integer,
- port: :integer,
- serve: :boolean
- ]
- )
-
- if invalid != [] do
- Mix.raise(usage())
- end
-
- format = Keyword.get(opts, :format, "summary")
- category = Keyword.get(opts, :category)
- request = request_opts(positional, category)
-
- if Keyword.get(opts, :serve, false) do
- serve_demo(request,
- host: Keyword.get(opts, :host, LiveUi.Demo.default_host()),
- linger_ms: Keyword.get(opts, :linger_ms),
- port: Keyword.get(opts, :port, LiveUi.Demo.default_port())
- )
- else
- case {format, positional} do
- {"catalog", []} ->
- Mix.shell().info(format_catalog())
-
- {chosen_format, []} ->
- run_demo(chosen_format, request)
-
- {chosen_format, ["home"]} ->
- run_demo(chosen_format, request)
-
- {chosen_format, [_example_id]} ->
- run_demo(chosen_format, request)
-
- _other ->
- Mix.raise(usage())
- end
- end
- end
-
- defp run_demo(format, opts) do
- case LiveUi.Demo.run(opts) do
- {:ok, execution} ->
- Mix.shell().info(format_execution(execution, format))
-
- {:error, reason} ->
- Mix.raise("unable to render live_ui demo: #{inspect(reason)}")
- end
- end
-
- defp serve_demo(request, opts) do
- serve_opts =
- request ++
- [
- host: Keyword.fetch!(opts, :host),
- port: Keyword.fetch!(opts, :port)
- ]
-
- case LiveUi.Demo.serve(serve_opts) do
- {:ok, launch} ->
- Mix.shell().info("""
- LiveUi demo server
- url: #{launch.url}
- press Ctrl+C twice to stop
- """)
-
- try do
- await_shutdown(Keyword.get(opts, :linger_ms))
- after
- stop_server(launch.server)
- end
-
- {:error, reason} ->
- Mix.raise("unable to launch live_ui demo server: #{inspect(reason)}")
- end
- end
-
- defp format_execution(execution, "summary") do
- [
- "LiveUi demo summary",
- " view: #{execution.view}",
- " category: #{execution.selected_category}",
- " selected widget: #{selected_example_label(execution.selected_example)}",
- " widgets in category: #{length(execution.examples)}",
- " total widgets: #{execution.total_examples}"
- ]
- |> Enum.join("\n")
- end
-
- defp format_execution(execution, "html"), do: execution.html
-
- defp format_execution(execution, "report") do
- inspect(execution, pretty: true, width: 100, limit: :infinity, sort_maps: true)
- end
-
- defp format_execution(_execution, other) do
- Mix.raise("unsupported demo format #{inspect(other)}")
- end
-
- defp format_catalog do
- LiveUi.Demo.catalog()
- |> inspect(pretty: true, width: 100, limit: :infinity, sort_maps: true)
- end
-
- defp request_opts([], category), do: [category: category]
- defp request_opts(["home"], category), do: [category: category]
- defp request_opts([example_id], category), do: [category: category, example: example_id]
- defp request_opts(_other, _category), do: Mix.raise(usage())
-
- defp await_shutdown(nil) do
- receive do
- after
- :infinity -> :ok
- end
- end
-
- defp await_shutdown(milliseconds) when is_integer(milliseconds) and milliseconds >= 0 do
- Process.sleep(milliseconds)
- end
-
- defp stop_server(server) when is_pid(server) do
- if Process.alive?(server) do
- Supervisor.stop(server)
- end
- end
-
- defp usage do
- "usage: mix live_ui.demo [home|EXAMPLE_ID] [--format summary|html|report|catalog] [--serve --port PORT --host HOST]"
- end
-
- defp selected_example_label(nil), do: "none"
- defp selected_example_label(example), do: to_string(example.id)
-end
diff --git a/packages/live_ui/lib/mix/tasks/live_ui.export.ex b/packages/live_ui/lib/mix/tasks/live_ui.export.ex
index 4d713f22..55c99378 100644
--- a/packages/live_ui/lib/mix/tasks/live_ui.export.ex
+++ b/packages/live_ui/lib/mix/tasks/live_ui.export.ex
@@ -6,11 +6,11 @@ defmodule Mix.Tasks.LiveUi.Export do
@moduledoc """
Prints a stable exported representation of a maintained `live_ui` example.
- mix live_ui.export native_styled_profile
- mix live_ui.export native_styled_profile --format artifact
- mix live_ui.export canonical_styled_operations --format html
- mix live_ui.export native_styled_profile --format comparison
- mix live_ui.export native_styled_profile --format style
+ mix live_ui.export button
+ mix live_ui.export button --format artifact
+ mix live_ui.export table --format html
+ mix live_ui.export button --format comparison
+ mix live_ui.export button --format style
mix live_ui.export --format catalog
"""
diff --git a/packages/live_ui/lib/mix/tasks/live_ui.inspect.ex b/packages/live_ui/lib/mix/tasks/live_ui.inspect.ex
index 25a98fd5..c5927a2a 100644
--- a/packages/live_ui/lib/mix/tasks/live_ui.inspect.ex
+++ b/packages/live_ui/lib/mix/tasks/live_ui.inspect.ex
@@ -6,10 +6,10 @@ defmodule Mix.Tasks.LiveUi.Inspect do
@moduledoc """
Prints inspection output for a maintained `live_ui` example.
- mix live_ui.inspect native_styled_profile
- mix live_ui.inspect native_styled_operations --format diagnostics
- mix live_ui.inspect canonical_styled_profile --format style
- mix live_ui.inspect canonical_styled_profile --format comparison
+ mix live_ui.inspect button
+ mix live_ui.inspect table --format diagnostics
+ mix live_ui.inspect button --format style
+ mix live_ui.inspect button --format comparison
mix live_ui.inspect --format catalog
"""
diff --git a/packages/live_ui/lib/mix/tasks/live_ui.preview.ex b/packages/live_ui/lib/mix/tasks/live_ui.preview.ex
index d456514d..a7571ec8 100644
--- a/packages/live_ui/lib/mix/tasks/live_ui.preview.ex
+++ b/packages/live_ui/lib/mix/tasks/live_ui.preview.ex
@@ -7,10 +7,10 @@ defmodule Mix.Tasks.LiveUi.Preview do
Prints preview output for a maintained `live_ui` example.
mix live_ui.preview
- mix live_ui.preview native_styled_profile
- mix live_ui.preview native_styled_profile --format artifact
- mix live_ui.preview canonical_styled_operations --format html
- mix live_ui.preview styled_continuity_compare --format report
+ mix live_ui.preview button
+ mix live_ui.preview button --format artifact
+ mix live_ui.preview table --format html
+ mix live_ui.preview command_palette --format report
"""
alias LiveUi.Export
diff --git a/packages/live_ui/test/live_ui/aligned_examples_test.exs b/packages/live_ui/test/live_ui/aligned_examples_test.exs
new file mode 100644
index 00000000..b896eb5e
--- /dev/null
+++ b/packages/live_ui/test/live_ui/aligned_examples_test.exs
@@ -0,0 +1,92 @@
+defmodule LiveUi.AlignedExamplesTest do
+ use ExUnit.Case, async: true
+
+ import Phoenix.LiveViewTest
+
+ test "aligned inventory matches the root examples directory one for one" do
+ root_ids = LiveUi.Examples.repository_example_ids()
+ aligned_ids = LiveUi.Examples.aligned_example_ids() |> Enum.sort()
+
+ assert aligned_ids == root_ids
+ refute Enum.any?(aligned_ids, &String.starts_with?(Atom.to_string(&1), "native_"))
+ refute Enum.any?(aligned_ids, &String.starts_with?(Atom.to_string(&1), "canonical_"))
+ refute Enum.any?(aligned_ids, &String.ends_with?(Atom.to_string(&1), "_compare"))
+ end
+
+ test "aligned catalog entries point at package-local live_ui screen modules" do
+ for example <- LiveUi.Examples.aligned_catalog() do
+ module_name = Atom.to_string(example.module)
+
+ assert String.starts_with?(module_name, "Elixir.LiveUi.Examples.Aligned.")
+ assert example.path == :aligned
+ assert example.package_specialization?
+ assert example.runtime_obligations.server_authoritative?
+ assert example.runtime_obligations.direct_native?
+ assert example.runtime_obligations.root_example_id == example.id
+ assert is_list(example.runtime_obligations.native_widget_kinds)
+ assert example.runtime_obligations.native_widget_kinds != []
+
+ exported = example.module.__info__(:functions)
+
+ assert {:render, 1} in exported
+ assert {:id, 0} in exported
+ assert {:metadata, 0} in exported
+ end
+ end
+
+ test "aligned screens render native live_ui widget surfaces instead of root example wrappers" do
+ for example <- LiveUi.Examples.aligned_catalog() do
+ module = example.module
+ html = render_component(fn assigns -> module.render(assigns) end, module.mount_defaults())
+
+ assert html =~ ~s(data-live-ui-widget=)
+ refute html =~ "UnifiedExamples."
+ refute html =~ "data-unified-ui"
+ refute html =~ "data-unified-iur"
+ end
+ end
+
+ test "aligned screens keep representative specialized widget mappings visible" do
+ assert {:ok, checkbox} = LiveUi.Examples.find_aligned(:checkbox)
+ assert {:ok, overlay} = LiveUi.Examples.find_aligned("overlay")
+ assert {:ok, field_group} = LiveUi.Examples.find_aligned(:field_group)
+ assert {:ok, grid} = LiveUi.Examples.find_aligned(:grid)
+ assert {:ok, pick_list} = LiveUi.Examples.find_aligned(:pick_list)
+
+ checkbox_html =
+ render_component(
+ fn assigns -> checkbox.module.render(assigns) end,
+ checkbox.module.mount_defaults()
+ )
+
+ overlay_html =
+ render_component(
+ fn assigns -> overlay.module.render(assigns) end,
+ overlay.module.mount_defaults()
+ )
+
+ field_group_html =
+ render_component(
+ fn assigns -> field_group.module.render(assigns) end,
+ field_group.module.mount_defaults()
+ )
+
+ grid_html =
+ render_component(
+ fn assigns -> grid.module.render(assigns) end,
+ grid.module.mount_defaults()
+ )
+
+ pick_list_html =
+ render_component(
+ fn assigns -> pick_list.module.render(assigns) end,
+ pick_list.module.mount_defaults()
+ )
+
+ assert checkbox_html =~ ~s(data-live-ui-widget="toggle")
+ assert overlay_html =~ ~s(data-live-ui-widget="overlay-surface")
+ assert field_group_html =~ ~s(data-live-ui-widget="field-group")
+ assert grid_html =~ ~s(data-live-ui-widget="grid")
+ assert pick_list_html =~ ~s(data-live-ui-widget="select")
+ end
+end
diff --git a/packages/live_ui/test/live_ui/demo_interaction_test.exs b/packages/live_ui/test/live_ui/demo_interaction_test.exs
deleted file mode 100644
index 4909449e..00000000
--- a/packages/live_ui/test/live_ui/demo_interaction_test.exs
+++ /dev/null
@@ -1,114 +0,0 @@
-defmodule LiveUi.DemoInteractionTest do
- use ExUnit.Case, async: true
-
- import Phoenix.LiveViewTest
-
- alias LiveUi.Runtime.ScreenComponent
- alias UnifiedIUR.Interaction
-
- test "button examples auto-wire canonical click interactions through the demo hook" do
- runtime_state = demo_runtime_state(example: :button)
-
- assert {:noreply, updated_socket} =
- ScreenComponent.handle_event(
- "canonical_interaction",
- %{
- "interaction" => encode_interaction(Interaction.click(intent: :widget_demo_button)),
- "element_id" => "live-ui-demo-widget-button-button",
- "widget" => "button"
- },
- demo_socket(runtime_state)
- )
-
- assert updated_socket.assigns.runtime_state.assigns.widget_demo_state.button.clicks == 1
-
- html =
- render_component(LiveUi.Runtime.component(),
- id: "live-ui-demo-runtime",
- runtime_state: updated_socket.assigns.runtime_state,
- show_demo_panels: false
- )
-
- assert html =~ ~s(phx-click="canonical_interaction")
- assert html =~ "Clicks recorded: 1"
- end
-
- test "text input examples auto-wire canonical change interactions through the demo hook" do
- runtime_state = demo_runtime_state(example: :text_input)
-
- assert {:noreply, updated_socket} =
- ScreenComponent.handle_event(
- "canonical_interaction",
- %{
- "interaction" =>
- encode_interaction(
- Interaction.change(intent: :widget_demo_text_input, binding: :widget_name)
- ),
- "element_id" => "live-ui-demo-widget-text_input-text-input",
- "widget" => "text_input",
- "widget_name" => "Signals"
- },
- demo_socket(runtime_state)
- )
-
- assert updated_socket.assigns.runtime_state.assigns.widget_demo_state.text_input.value ==
- "Signals"
-
- html =
- render_component(LiveUi.Runtime.component(),
- id: "live-ui-demo-runtime",
- runtime_state: updated_socket.assigns.runtime_state,
- show_demo_panels: false
- )
-
- assert html =~ "canonical_interaction"
- assert html =~ "Current value: Signals"
- end
-
- test "tabs examples auto-wire canonical selection interactions through the demo hook" do
- runtime_state = demo_runtime_state(example: :tabs)
-
- assert {:noreply, updated_socket} =
- ScreenComponent.handle_event(
- "canonical_interaction",
- %{
- "interaction" =>
- encode_interaction(Interaction.selection(intent: :widget_demo_tabs)),
- "element_id" => "live-ui-demo-widget-tabs-tabs",
- "widget" => "tabs",
- "item_id" => "signals"
- },
- demo_socket(runtime_state)
- )
-
- assert updated_socket.assigns.runtime_state.assigns.widget_demo_state.tabs.active == "signals"
-
- html =
- render_component(LiveUi.Runtime.component(),
- id: "live-ui-demo-runtime",
- runtime_state: updated_socket.assigns.runtime_state,
- show_demo_panels: false
- )
-
- assert html =~ ~s(phx-click="canonical_interaction")
- assert html =~ ~s(data-item-id="signals")
- assert html =~ "Active tab: Signals"
- end
-
- defp demo_runtime_state(opts) do
- {:ok, assigns} = LiveUi.Demo.screen_assigns(opts)
- {:ok, runtime_state} = LiveUi.Runtime.mount(LiveUi.Demo.screen(), assigns: assigns)
- runtime_state
- end
-
- defp demo_socket(runtime_state) do
- %Phoenix.LiveView.Socket{}
- |> Phoenix.Component.assign(:runtime_state, runtime_state)
- end
-
- defp encode_interaction(%Interaction{} = interaction) do
- interaction
- |> :erlang.term_to_binary()
- |> Base.url_encode64(padding: false)
- end
-end
diff --git a/packages/live_ui/test/live_ui/demo_server_test.exs b/packages/live_ui/test/live_ui/demo_server_test.exs
deleted file mode 100644
index e9303cd4..00000000
--- a/packages/live_ui/test/live_ui/demo_server_test.exs
+++ /dev/null
@@ -1,82 +0,0 @@
-defmodule LiveUi.DemoServerTest do
- use ExUnit.Case, async: false
-
- @moduletag :capture_log
-
- test "browser host renders the demo home screen" do
- port = free_port()
- {:ok, _server} = start_supervised({LiveUi.Demo.Server, host: "127.0.0.1", port: port})
-
- body = wait_for_body("http://127.0.0.1:#{port}/")
-
- assert body =~ "Live UI Workbench"
- assert body =~ "live_ui package demo"
-
- assert body =~
- "Browse the package-local demo through the same server-authoritative runtime the package exposes everywhere else."
-
- assert body =~ "data-phx-main"
- assert body =~ ~s(id="live-ui-demo-category-tabs")
- assert body =~ ~s(data-item-id="button")
- assert body =~ ">Button<"
- refute body =~ "Meaningful Interaction Story"
- refute body =~ "Canonical Signal Preview"
- refute body =~ "No signal captured yet"
- refute body =~ "Live UI Demo
"
- refute body =~ ">Overview<"
- refute body =~ "Examples:"
- refute body =~ "Native:"
- refute body =~ "Canonical:"
- refute body =~ "Mixed:"
- refute body =~ "Featured demo routes"
- end
-
- test "browser host deep-links directly to an example" do
- port = free_port()
- {:ok, _server} = start_supervised({LiveUi.Demo.Server, host: "127.0.0.1", port: port})
-
- body = wait_for_body("http://127.0.0.1:#{port}/examples/button")
-
- assert body =~ "Button"
- assert body =~ ~s(id="live-ui-demo-widget-button-panel")
- assert body =~ ~s(id="live-ui-demo-widget-button-button")
- assert body =~ ~s(data-live-ui-widget="button")
-
- assert body =~
- "Browse the package-local demo through the same server-authoritative runtime the package exposes everywhere else."
-
- refute body =~ "live-ui-demo-example-breadcrumbs"
- refute body =~ ~s(id="live-ui-demo-interaction-grid")
- refute body =~ "Meaningful Interaction Story"
- refute body =~ "Canonical Signal Preview"
- refute body =~ "No signal captured yet"
- refute body =~ "Rendered Preview"
- refute body =~ "Comparison Report"
- assert body =~ ~s(id="live-ui-demo-example-metadata")
- assert body =~ "Category: Foundational | Widget family: Content"
- end
-
- defp wait_for_body(url, attempts \\ 20)
-
- defp wait_for_body(_url, 0) do
- flunk("demo server did not become ready in time")
- end
-
- defp wait_for_body(url, attempts) do
- case System.cmd("curl", ["--fail", "--silent", "--show-error", url]) do
- {body, 0} ->
- body
-
- _other ->
- Process.sleep(50)
- wait_for_body(url, attempts - 1)
- end
- end
-
- defp free_port do
- {:ok, socket} = :gen_tcp.listen(0, [:binary, active: false, packet: :raw, reuseaddr: true])
- {:ok, port} = :inet.port(socket)
- :ok = :gen_tcp.close(socket)
- port
- end
-end
diff --git a/packages/live_ui/test/live_ui/demo_test.exs b/packages/live_ui/test/live_ui/demo_test.exs
deleted file mode 100644
index f5f858d6..00000000
--- a/packages/live_ui/test/live_ui/demo_test.exs
+++ /dev/null
@@ -1,91 +0,0 @@
-defmodule LiveUi.DemoTest do
- use ExUnit.Case, async: true
-
- test "home workbench renders the package-local demo shell" do
- assert {:ok, demo} = LiveUi.Demo.run()
-
- assert demo.screen == LiveUi.Demo.Screen
- assert demo.view == :home
- assert demo.selected_category == :foundational
- assert demo.selected_example == nil
- assert demo.total_examples > 0
- assert demo.html =~ "Live UI Workbench"
- assert demo.html =~ "live_ui package demo"
-
- assert demo.html =~
- "Browse the package-local demo through the same server-authoritative runtime the package exposes everywhere else."
-
- assert demo.html =~ ~s(data-live-ui-widget="screen-shell")
- assert demo.html =~ ~s(data-live-ui-widget="tabs")
- assert demo.html =~ "Current category: Foundational"
- refute demo.html =~ "Meaningful Interaction Story"
- refute demo.html =~ "Canonical Signal Preview"
- refute demo.html =~ "No signal captured yet"
- refute demo.html =~ "Examples in this category"
- refute demo.html =~ "Featured example:"
- refute demo.html =~ "Widget:"
- refute demo.html =~ "Live UI Demo
"
- refute demo.html =~ ">Overview<"
- refute demo.html =~ "Examples:"
- refute demo.html =~ "Native:"
- refute demo.html =~ "Canonical:"
- refute demo.html =~ "Mixed:"
- refute demo.html =~ "Maintained Runtime Surfaces"
- refute demo.html =~ "One workbench across native, canonical, and mixed example paths."
-
- assert demo.html =~ ~s(id="live-ui-demo-category-tabs")
- assert demo.html =~ ~s(data-item-id="button")
- assert demo.html =~ ">Button<"
-
- refute demo.html =~ ~s(phx-click="select_example")
- end
-
- test "widget example workbench renders the selected widget surface" do
- assert {:ok, demo} = LiveUi.Demo.run(example: :button)
-
- assert demo.view == :example
- assert demo.selected_example.id == :button
- assert demo.selected_example.title == "Button"
- assert demo.preview == nil
- assert demo.html =~ ~s(id="live-ui-demo-example-title")
- assert demo.html =~ ~s(id="live-ui-demo-widget-button-panel")
- assert demo.html =~ ~s(id="live-ui-demo-widget-button-button")
- assert demo.html =~ ~s(data-live-ui-widget="button")
- refute demo.html =~ "live-ui-demo-example-breadcrumbs"
- refute demo.html =~ "Back To Overview"
- assert demo.html =~ ~s(id="live-ui-demo-category-tabs")
- refute demo.html =~ ~s(id="live-ui-demo-interaction-grid")
- refute demo.html =~ "Meaningful Interaction Story"
- refute demo.html =~ "Canonical Signal Preview"
- refute demo.html =~ "No signal captured yet"
- refute demo.html =~ "Review Metadata"
- refute demo.html =~ ~s(id="live-ui-demo-metadata-card")
- refute demo.html =~ "Rendered Preview"
- refute demo.html =~ "Comparison Report"
- assert demo.html =~ ~s(id="live-ui-demo-example-metadata")
- assert demo.html =~ "Category: Foundational | Widget family: Content"
- end
-
- test "widgets from other categories also render through the same shell" do
- assert {:ok, demo} = LiveUi.Demo.run(example: :toast)
-
- assert demo.selected_example.id == :toast
- assert demo.selected_category == :overlay
- assert demo.preview == nil
- refute demo.html =~ "Comparison Report"
- assert demo.html =~ "Current category: Overlay"
- assert demo.html =~ ~s(id="live-ui-demo-example-metadata")
- assert demo.html =~ "Category: Overlay | Widget family: Overlay"
- assert demo.html =~ ~s(id="live-ui-demo-widget-toast-toast")
- assert demo.html =~ ~s(data-live-ui-widget="toast")
- end
-
- test "browser host paths support home categories and deep-linked examples" do
- assert LiveUi.Demo.path() == "/"
- assert LiveUi.Demo.path(category: :navigation) == "/?category=navigation"
- assert LiveUi.Demo.path(example: :button) == "/examples/button"
-
- assert LiveUi.Demo.path(example: :button, category: :foundational) ==
- "/examples/button?category=foundational"
- end
-end
diff --git a/packages/live_ui/test/live_ui/documentation_alignment_test.exs b/packages/live_ui/test/live_ui/documentation_alignment_test.exs
new file mode 100644
index 00000000..2b10f7d3
--- /dev/null
+++ b/packages/live_ui/test/live_ui/documentation_alignment_test.exs
@@ -0,0 +1,12 @@
+defmodule LiveUi.DocumentationAlignmentTest do
+ use ExUnit.Case, async: true
+
+ test "documentation surface describes aligned examples and omits retired demo language" do
+ docs = LiveUi.Tooling.documentation_surface()
+
+ assert docs.complete?
+ assert docs.missing_paths == []
+ assert docs.missing_snippets == %{}
+ assert docs.prohibited_mentions == []
+ end
+end
diff --git a/packages/live_ui/test/live_ui/examples_test.exs b/packages/live_ui/test/live_ui/examples_test.exs
index e45fc021..0f23c1f4 100644
--- a/packages/live_ui/test/live_ui/examples_test.exs
+++ b/packages/live_ui/test/live_ui/examples_test.exs
@@ -2,181 +2,59 @@ defmodule LiveUi.ExamplesTest do
use ExUnit.Case, async: true
import Phoenix.LiveViewTest
- alias Jido.Signal
- test "example catalog keeps native and canonical foundational paths paired" do
+ test "public example catalog now exposes aligned focused ids only" do
catalog = LiveUi.Examples.catalog()
+ example_ids = Enum.map(catalog, & &1.id)
+
+ assert :button in example_ids
+ assert :table in example_ids
+ assert :command_palette in example_ids
+ assert :overlay in example_ids
+ refute Enum.any?(example_ids, &String.starts_with?(Atom.to_string(&1), "native_"))
+ refute Enum.any?(example_ids, &String.starts_with?(Atom.to_string(&1), "canonical_"))
+ refute Enum.any?(example_ids, &String.ends_with?(Atom.to_string(&1), "_compare"))
+ end
- assert Enum.any?(
- catalog,
- &(&1.id == :native_display and &1.comparable_to == :canonical_display)
- )
-
- assert Enum.any?(catalog, &(&1.id == :canonical_form and &1.comparable_to == :native_form))
-
- assert Enum.any?(
- catalog,
- &(&1.id == :canonical_navigation and &1.comparable_to == :native_navigation)
- )
-
- assert Enum.any?(
- catalog,
- &(&1.id == :native_boundary and &1.comparable_to == :canonical_boundary)
- )
-
- assert Enum.any?(
- catalog,
- &(&1.id == :canonical_boundary and &1.comparable_to == :native_boundary)
- )
-
- assert Enum.any?(
- catalog,
- &(&1.id == :native_styled_profile and &1.comparable_to == :canonical_styled_profile)
- )
-
- assert Enum.any?(
- catalog,
- &(&1.id == :canonical_styled_profile and &1.comparable_to == :native_styled_profile)
- )
-
- assert Enum.any?(
- catalog,
- &(&1.id == :native_styled_operations and
- &1.comparable_to == :canonical_styled_operations)
- )
-
- assert Enum.any?(
- catalog,
- &(&1.id == :canonical_styled_operations and
- &1.comparable_to == :native_styled_operations)
- )
+ test "public example lookup resolves aligned ids and rejects retired package-only ids" do
+ assert {:ok, button} = LiveUi.Examples.find(:button)
+ assert button.path == :aligned
+ assert button.module == LiveUi.Examples.Aligned.Button
- assert Enum.any?(catalog, &(&1.id == :boundary_transport_compare and &1.path == :mixed))
- assert Enum.any?(catalog, &(&1.id == :styled_continuity_compare and &1.path == :mixed))
+ assert {:ok, table} = LiveUi.Examples.find("table")
+ assert table.path == :aligned
- assert Enum.any?(
- catalog,
- &(&1.id == :web_navigation_transition_compare and &1.path == :mixed)
- )
+ assert :error = LiveUi.Examples.find(:native_display)
+ assert :error = LiveUi.Examples.find("canonical_form")
+ assert :error = LiveUi.Examples.find("styled_continuity_compare")
end
- test "native and canonical examples render through their intended package paths" do
+ test "aligned examples keep native rendering and canonical review on the same ids" do
+ assert {:ok, button} = LiveUi.Examples.find(:button)
+ assert {:ok, button_canonical} = LiveUi.Examples.canonical_element(:button)
+
native_html =
- render_component(&LiveUi.Examples.NativeFormScreen.render/1, %{name: "Pascal"})
+ render_component(
+ fn assigns -> button.module.render(assigns) end,
+ button.module.mount_defaults()
+ )
canonical_html =
render_component(&LiveUi.Renderer.render/1, %{
- element: LiveUi.Examples.CanonicalForm.element()
+ element: button_canonical
})
- assert native_html =~ "data-live-ui-widget=\"form-builder\""
- assert canonical_html =~ "data-live-ui-widget=\"form-builder\""
- assert canonical_html =~ "Pascal"
+ assert native_html =~ "data-live-ui-widget=\"button\""
+ assert canonical_html =~ "data-live-ui-widget=\"button\""
end
- test "transport examples keep local, native-boundary, and canonical-boundary flows visible" do
- assert {:ok, native_local} =
- LiveUi.Transport.translate_native(
- LiveUi.Examples.NativeBoundaryScreen.local_event_example()
- )
-
- assert native_local.signal == nil
-
- assert {:ok, native_boundary} =
- LiveUi.Transport.translate_native(
- LiveUi.Examples.NativeBoundaryScreen.boundary_event_example()
- )
-
- assert %Signal{} = native_boundary.signal
-
- assert {:ok, canonical_boundary} = LiveUi.Examples.CanonicalBoundaryProfile.translation()
- assert %Signal{} = canonical_boundary.signal
-
- assert {:ok, comparison} = LiveUi.Examples.MixedBoundaryTransport.compare_paths()
-
- assert comparison.native_local.signal == nil
- assert comparison.native_boundary.runtime_event == "rename"
- assert comparison.runtime_action.runtime_event == "rename"
- end
-
- test "tooling exposes maintained example metadata" do
+ test "tooling exposes aligned example metadata" do
example_ids = Enum.map(LiveUi.Tooling.examples(), & &1.id)
- assert :native_display in example_ids
- assert :canonical_display in example_ids
- assert :boundary_transport_compare in example_ids
- assert :native_styled_profile in example_ids
- assert :canonical_styled_operations in example_ids
- assert :styled_continuity_compare in example_ids
- assert :web_navigation_transition_compare in example_ids
- end
-
- test "example catalog provides stable preview and review metadata" do
- assert {:ok, native_profile} = LiveUi.Examples.find(:native_styled_profile)
- assert {:ok, styled_compare} = LiveUi.Examples.find("styled_continuity_compare")
-
- assert native_profile.preview_id == "native:native_styled_profile"
- assert native_profile.review_artifact == "live_ui/native/native_styled_profile"
- assert native_profile.coverage.native?
- refute native_profile.coverage.canonical?
- assert native_profile.coverage.continuity?
- assert native_profile.runtime_obligations.server_authoritative?
-
- assert styled_compare.path == :mixed
- assert styled_compare.coverage.native?
- assert styled_compare.coverage.canonical?
- assert styled_compare.coverage.transport?
- assert styled_compare.runtime_obligations.canonical_boundary?
-
- grouped = LiveUi.Examples.grouped_catalog()
-
- assert Enum.any?(grouped.native, &(&1.id == :native_display))
- assert Enum.any?(grouped.canonical, &(&1.id == :canonical_display))
- assert Enum.any?(grouped.mixed, &(&1.id == :styled_continuity_compare))
- assert Enum.any?(grouped.mixed, &(&1.id == :web_navigation_transition_compare))
- end
-
- test "styled continuity examples keep native and canonical paths aligned" do
- assert {:ok, continuity} = LiveUi.Examples.StyledContinuityComparison.compare()
-
- assert continuity.profile.continuity.widgets_aligned?
- assert continuity.profile.continuity.browser_style_aligned?
- assert continuity.profile.continuity.runtime_model_aligned?
- assert continuity.profile.diagnostics == []
- assert "box" in continuity.profile.shared_widgets
- assert "text" in continuity.profile.shared_widgets
- assert "text-input" in continuity.profile.shared_widgets
- assert "button" in continuity.profile.shared_widgets
-
- assert continuity.operations.continuity.widgets_aligned?
- assert continuity.operations.continuity.browser_style_aligned?
- assert continuity.operations.continuity.runtime_model_aligned?
- assert continuity.operations.diagnostics == []
- assert "overlay-surface" in continuity.operations.shared_widgets
- assert "viewport" in continuity.operations.shared_widgets
- assert "canvas" in continuity.operations.shared_widgets
- assert "dialog" in continuity.operations.shared_widgets
- assert "cluster-dashboard" in continuity.operations.shared_widgets
-
- assert continuity.boundary.runtime_action.runtime_event == "rename"
- end
-
- test "web navigation transition example keeps screen, modal, replacement, and host-route meaning aligned" do
- assert {:ok, comparison} = LiveUi.Examples.WebNavigationTransitionComparison.compare()
-
- assert comparison.native.after_navigate.screen_id == :settings
- assert comparison.native.after_modal.current_modal.modal == :settings_dialog
- assert comparison.native.after_replace.screen_id == :home
- assert comparison.canonical.after_navigate.screen_id == :settings
- assert comparison.canonical.after_replace.mode == :canonical
- assert comparison.continuity.same_navigation_target?
- assert comparison.continuity.same_modal_identifier?
- assert comparison.continuity.same_replacement_target?
- assert comparison.continuity.replacement_clears_modal?
- assert comparison.continuity.host_route_externalized?
- assert comparison.continuity.server_authoritative?
-
- assert comparison.host_route_fixture.host_application.live_view_route.path ==
- "/workspace/settings"
+ assert :button in example_ids
+ assert :checkbox in example_ids
+ assert :table in example_ids
+ assert :command_palette in example_ids
+ refute :native_display in example_ids
end
end
diff --git a/packages/live_ui/test/live_ui/export_test.exs b/packages/live_ui/test/live_ui/export_test.exs
index 896efadb..8f5087dc 100644
--- a/packages/live_ui/test/live_ui/export_test.exs
+++ b/packages/live_ui/test/live_ui/export_test.exs
@@ -2,35 +2,35 @@ defmodule LiveUi.ExportTest do
use ExUnit.Case, async: true
test "export can render stable metadata and html for maintained examples" do
- assert {:ok, metadata} = LiveUi.Export.example(:native_styled_profile, :metadata)
- assert metadata =~ "native_styled_profile"
+ assert {:ok, metadata} = LiveUi.Export.example(:button, :metadata)
+ assert metadata =~ "button"
assert metadata =~ "preview_id"
- assert {:ok, html} = LiveUi.Export.example(:canonical_styled_operations, :html)
- assert html =~ "data-live-ui-widget=\"overlay-surface\""
- assert html =~ "data-live-ui-widget=\"cluster-dashboard\""
+ assert {:ok, html} = LiveUi.Export.example(:table, :html)
+ assert html =~ "data-live-ui-widget=\"table\""
+ assert html =~ "data-live-ui-widget=\"screen-shell\""
end
test "export can print comparison and diagnostics output for paired examples" do
- assert {:ok, comparison} = LiveUi.Export.example(:native_styled_operations, :comparison)
- assert comparison =~ "native_styled_operations"
- assert comparison =~ "canonical_styled_operations"
+ assert {:ok, comparison} = LiveUi.Export.example(:button, :comparison)
+ assert comparison =~ "button"
+ assert comparison =~ "Button Canonical Review"
assert comparison =~ "widgets_aligned?"
assert comparison =~ "browser_style_aligned?"
assert comparison =~ "browser_style"
- assert {:ok, diagnostics} = LiveUi.Export.example(:native_styled_profile, :diagnostics)
+ assert {:ok, diagnostics} = LiveUi.Export.example(:button, :diagnostics)
assert diagnostics =~ "diagnostics"
- assert diagnostics =~ "canonical_styled_profile"
+ assert diagnostics =~ "Button Canonical Review"
assert diagnostics =~ "native_browser_style"
end
test "export can print style-focused output and browser-style artifacts" do
- assert {:ok, style} = LiveUi.Export.example(:native_styled_profile, :style)
+ assert {:ok, style} = LiveUi.Export.example(:button, :style)
assert style =~ "browser_style_nodes"
assert style =~ "realized_entry_ids"
- assert {:ok, artifact} = LiveUi.Export.example(:native_styled_profile, :artifact)
+ assert {:ok, artifact} = LiveUi.Export.example(:button, :artifact)
assert artifact =~ "browser_style_nodes"
assert artifact =~ "html"
assert artifact =~ "canonical"
diff --git a/packages/live_ui/test/live_ui/phase_17_integration_test.exs b/packages/live_ui/test/live_ui/phase_17_integration_test.exs
index f46580af..0cc04ded 100644
--- a/packages/live_ui/test/live_ui/phase_17_integration_test.exs
+++ b/packages/live_ui/test/live_ui/phase_17_integration_test.exs
@@ -4,13 +4,13 @@ defmodule LiveUi.Phase17IntegrationTest do
import Phoenix.LiveViewTest
alias LiveUi.{Info, Runtime, Widgets}
- alias UnifiedIUR.Widgets.{Advanced, Data, Foundational}
+ alias UnifiedIUR.Widgets.{Data, Foundational}
@moduledoc """
- Integration tests for Phase 17 - Tooling, Demo, Validation, and Release Readiness.
+ Integration tests for Phase 17 - Tooling, Validation, and Release Readiness.
These tests validate the widget-component architecture is properly exposed
- in tooling, demos work correctly, and validation gates are in place.
+ in tooling, aligned examples work correctly, and validation gates are in place.
"""
describe "17.1 - Tooling and Inspection for Widget Components" do
@@ -81,19 +81,17 @@ defmodule LiveUi.Phase17IntegrationTest do
end
end
- describe "17.2 - Demo and Maintained Example Realignment" do
- test "demo workbench renders widgets correctly" do
- {:ok, demo} = LiveUi.Demo.run(example: :text)
+ describe "17.2 - Aligned Focused Examples and Demo Retirement" do
+ test "aligned examples render widgets correctly" do
+ {:ok, preview} = LiveUi.Tooling.preview_example(:text)
- # Widget should be rendered with proper attributes
- assert demo.html =~ ~s(data-live-ui-widget="text")
+ assert preview.result.html =~ ~s(data-live-ui-widget="text")
end
- test "demo exposes widget attributes for inspection" do
- {:ok, demo} = LiveUi.Demo.run(example: :button)
+ test "aligned examples expose widget attributes for inspection" do
+ {:ok, preview} = LiveUi.Tooling.preview_example(:button)
- # Widget should have identifying attributes
- assert demo.html =~ ~s(data-live-ui-widget="button")
+ assert preview.result.html =~ ~s(data-live-ui-widget="button")
end
test "canonical examples use widget component boundaries" do
@@ -133,22 +131,19 @@ defmodule LiveUi.Phase17IntegrationTest do
assert html =~ ~s(data-live-ui-widget-key=)
end
- test "demo catalog provides comprehensive widget coverage" do
- catalog = LiveUi.Demo.catalog()
+ test "aligned catalog provides comprehensive widget coverage" do
+ catalog = LiveUi.Examples.catalog()
- # Should have widgets from all families
- assert catalog.total_examples > 0
- assert is_list(catalog.categories)
- assert catalog.path_counts
+ assert length(catalog) == length(LiveUi.Examples.repository_example_ids())
+ assert Enum.all?(catalog, &(&1.path == :aligned))
+ assert Enum.all?(catalog, &(&1.runtime_obligations.root_example_id == &1.id))
end
- test "demo maintains separate native and canonical examples" do
- # Native path examples should work
- {:ok, native_demo} = LiveUi.Demo.run()
-
- assert native_demo.view == :home
- assert native_demo.screen == LiveUi.Demo.Screen
- assert is_binary(native_demo.html)
+ test "demo surface is retired and divergent example ids stay hidden" do
+ refute Code.ensure_loaded?(LiveUi.Demo)
+ assert :error = LiveUi.Examples.find(:native_display)
+ assert :error = LiveUi.Examples.find(:canonical_form)
+ assert :error = LiveUi.Examples.find(:styled_continuity_compare)
end
test "canonical rendering through Runtime preserves widget identity" do
diff --git a/packages/live_ui/test/live_ui/phase_18_integration_test.exs b/packages/live_ui/test/live_ui/phase_18_integration_test.exs
new file mode 100644
index 00000000..2d672133
--- /dev/null
+++ b/packages/live_ui/test/live_ui/phase_18_integration_test.exs
@@ -0,0 +1,54 @@
+defmodule LiveUi.Phase18IntegrationTest do
+ use ExUnit.Case, async: true
+
+ test "repository example ids align one for one with the live_ui package examples" do
+ report = LiveUi.Tooling.validation_report()
+
+ assert report.example_coverage.complete?
+ assert report.example_coverage.repository_example_ids == report.example_coverage.aligned_ids
+ assert report.example_coverage.missing_root_example_ids == []
+ assert report.example_coverage.unexpected_example_ids == []
+ end
+
+ test "native preview canonical comparison and export stay attached to the same aligned id" do
+ assert {:ok, preview} = LiveUi.Tooling.preview_example(:button)
+ assert {:ok, comparison} = LiveUi.Tooling.compare_example_pair(:button)
+ assert {:ok, diagnostics} = LiveUi.Export.example(:button, :diagnostics)
+
+ assert preview.example.id == :button
+ assert preview.result.path == :native
+ assert comparison.example.id == :button
+ assert comparison.canonical_example.id == :button
+ assert diagnostics =~ "Button Canonical Review"
+ end
+
+ test "demo and divergent package-only example ids are retired" do
+ refute Code.ensure_loaded?(LiveUi.Demo)
+ assert :error = LiveUi.Examples.find(:native_display)
+ assert :error = LiveUi.Examples.find(:canonical_form)
+ assert :error = LiveUi.Examples.find(:styled_continuity_compare)
+ end
+
+ test "validation and release readiness fail when aligned example inventory drifts" do
+ drifted_catalog = tl(LiveUi.Examples.catalog())
+ drifted = LiveUi.Tooling.validation_report(catalog: drifted_catalog)
+
+ refute drifted.example_coverage.complete?
+ refute drifted.release_readiness.ready?
+ assert drifted.example_coverage.missing_root_example_ids != []
+ end
+
+ test "validation and release readiness fail when docs reintroduce demo wording" do
+ polluted_docs = %{
+ "README.md" => File.read!("README.md") <> "\n\nmix live_ui.demo --serve\n"
+ }
+
+ drifted = LiveUi.Tooling.validation_report(documentation_opts: [doc_contents: polluted_docs])
+
+ refute drifted.documentation_surface.complete?
+ refute drifted.release_readiness.ready?
+
+ assert %{path: "README.md", label: "mix live_ui.demo"} in
+ drifted.documentation_surface.prohibited_mentions
+ end
+end
diff --git a/packages/live_ui/test/live_ui/phase_5_integration_test.exs b/packages/live_ui/test/live_ui/phase_5_integration_test.exs
index 293777a0..dece8e7b 100644
--- a/packages/live_ui/test/live_ui/phase_5_integration_test.exs
+++ b/packages/live_ui/test/live_ui/phase_5_integration_test.exs
@@ -75,10 +75,8 @@ defmodule LiveUi.Phase5IntegrationTest do
assert :styling_inspection in workflows
assert :continuity_comparison in workflows
- assert Enum.any?(
- examples,
- &(&1.id == :styled_continuity_compare and &1.path == :mixed)
- )
+ assert Enum.any?(examples, &(&1.id == :button and &1.path == :aligned))
+ refute Enum.any?(examples, &(&1.id == :styled_continuity_compare))
assert {:ok, report} =
LiveUi.Tooling.compare_native_and_canonical(
diff --git a/packages/live_ui/test/live_ui/phase_6_integration_test.exs b/packages/live_ui/test/live_ui/phase_6_integration_test.exs
index a2ebdebf..9296af79 100644
--- a/packages/live_ui/test/live_ui/phase_6_integration_test.exs
+++ b/packages/live_ui/test/live_ui/phase_6_integration_test.exs
@@ -3,38 +3,42 @@ defmodule LiveUi.Phase6IntegrationTest do
import ExUnit.CaptureIO
- test "maintained examples cover native canonical and mixed review workflows through one tooling surface" do
- assert {:ok, native_preview} = LiveUi.Tooling.preview_example(:native_styled_profile)
- assert {:ok, canonical_preview} = LiveUi.Tooling.preview_example(:canonical_styled_operations)
- assert {:ok, mixed_preview} = LiveUi.Tooling.preview_example(:styled_continuity_compare)
+ test "aligned examples cover native and canonical review workflows through one tooling surface" do
+ assert {:ok, native_preview} = LiveUi.Tooling.preview_example(:button)
+ assert {:ok, canonical_preview} =
+ LiveUi.Tooling.inspect_example(:button, review_mode: :canonical)
- assert native_preview.example.path == :native
- assert canonical_preview.example.path == :canonical
- assert mixed_preview.example.path == :mixed
+ assert {:ok, comparison} = LiveUi.Tooling.compare_example_pair(:button)
- assert native_preview.result.html =~ "data-live-ui-widget=\"box\""
- assert canonical_preview.result.html =~ "data-live-ui-widget=\"overlay-surface\""
- assert mixed_preview.result.operations.continuity.widgets_aligned?
- assert mixed_preview.result.boundary.runtime_action.runtime_event == "rename"
+ assert native_preview.example.path == :aligned
+ assert native_preview.result.path == :native
+ assert canonical_preview.example.path == :aligned
+ assert canonical_preview.result.path == :canonical
+
+ assert native_preview.result.html =~ "data-live-ui-widget=\"button\""
+ assert canonical_preview.result.html =~ "data-live-ui-widget=\"button\""
+ assert comparison.example.id == :button
+ assert comparison.canonical_example.id == :button
+ assert comparison.report.continuity.runtime_model_aligned?
end
test "preview inspect export and validate tasks provide one repeatable maintainer command path" do
preview =
capture_io(fn ->
Mix.Task.reenable("live_ui.preview")
- Mix.Tasks.LiveUi.Preview.run(["canonical_styled_profile", "--format", "html"])
+ Mix.Tasks.LiveUi.Preview.run(["button", "--format", "html"])
end)
inspect_output =
capture_io(fn ->
Mix.Task.reenable("live_ui.inspect")
- Mix.Tasks.LiveUi.Inspect.run(["native_styled_operations", "--format", "comparison"])
+ Mix.Tasks.LiveUi.Inspect.run(["button", "--format", "comparison"])
end)
export_output =
capture_io(fn ->
Mix.Task.reenable("live_ui.export")
- Mix.Tasks.LiveUi.Export.run(["styled_continuity_compare", "--format", "diagnostics"])
+ Mix.Tasks.LiveUi.Export.run(["button", "--format", "diagnostics"])
end)
validate_output =
@@ -43,11 +47,11 @@ defmodule LiveUi.Phase6IntegrationTest do
Mix.Tasks.LiveUi.Validate.run(["--format", "summary"])
end)
- assert preview =~ "data-live-ui-widget=\"box\""
- assert inspect_output =~ "native_styled_operations"
- assert inspect_output =~ "canonical_styled_operations"
- assert export_output =~ "styled_continuity_compare"
- assert export_output =~ "runtime_action"
+ assert preview =~ "data-live-ui-widget=\"button\""
+ assert inspect_output =~ "button"
+ assert inspect_output =~ "Button Canonical Review"
+ assert export_output =~ "diagnostics"
+ assert export_output =~ "Button Canonical Review"
assert validate_output =~ "LiveUi validation summary"
assert validate_output =~ "release ready?: true"
end
diff --git a/packages/live_ui/test/live_ui/phase_7_integration_test.exs b/packages/live_ui/test/live_ui/phase_7_integration_test.exs
index a1185cd9..b51d55ee 100644
--- a/packages/live_ui/test/live_ui/phase_7_integration_test.exs
+++ b/packages/live_ui/test/live_ui/phase_7_integration_test.exs
@@ -153,7 +153,7 @@ defmodule LiveUi.Phase7IntegrationTest do
end
end
- test "shared stylesheet remains package-owned and powers the browser demo host" do
+ test "shared stylesheet remains package-owned and powers aligned preview surfaces" do
assert LiveUi.stylesheet() == LiveUi.Stylesheet
css = LiveUi.Stylesheet.css()
@@ -168,14 +168,8 @@ defmodule LiveUi.Phase7IntegrationTest do
assert css =~ selector
end
- port = free_port()
- {:ok, _server} = start_supervised({LiveUi.Demo.Server, host: "127.0.0.1", port: port})
-
- body = wait_for_body("http://127.0.0.1:#{port}/")
-
- assert body =~ "Live UI Workbench"
- assert body =~ "--live-ui-theme-accent"
- assert body =~ ".live-ui-button.live-ui-button-solid"
+ assert {:ok, html} = LiveUi.Export.example(:button, :html)
+ assert html =~ "data-live-ui-widget=\"button\""
end
test "fallback hooks legacy attrs and diagnostics remain integration-stable" do
@@ -247,27 +241,4 @@ defmodule LiveUi.Phase7IntegrationTest do
}
end
- defp wait_for_body(url, attempts \\ 20)
-
- defp wait_for_body(_url, 0) do
- flunk("demo server did not become ready in time")
- end
-
- defp wait_for_body(url, attempts) do
- case System.cmd("curl", ["--fail", "--silent", "--show-error", url]) do
- {body, 0} ->
- body
-
- _other ->
- Process.sleep(50)
- wait_for_body(url, attempts - 1)
- end
- end
-
- defp free_port do
- {:ok, socket} = :gen_tcp.listen(0, [:binary, active: false, packet: :raw, reuseaddr: true])
- {:ok, port} = :inet.port(socket)
- :ok = :gen_tcp.close(socket)
- port
- end
end
diff --git a/packages/live_ui/test/live_ui/phase_9_integration_test.exs b/packages/live_ui/test/live_ui/phase_9_integration_test.exs
index c8766d5e..98135310 100644
--- a/packages/live_ui/test/live_ui/phase_9_integration_test.exs
+++ b/packages/live_ui/test/live_ui/phase_9_integration_test.exs
@@ -21,12 +21,12 @@ defmodule LiveUi.Phase9IntegrationTest do
end
test "style and artifact exports expose browser-realized output for review" do
- assert {:ok, style_output} = LiveUi.Export.example(:native_styled_profile, :style)
+ assert {:ok, style_output} = LiveUi.Export.example(:button, :style)
assert style_output =~ "browser_style"
assert style_output =~ "native_browser_style_nodes"
assert style_output =~ "realized_entry_ids"
- assert {:ok, artifact_output} = LiveUi.Export.example(:native_styled_profile, :artifact)
+ assert {:ok, artifact_output} = LiveUi.Export.example(:button, :artifact)
assert artifact_output =~ "browser_style_nodes"
assert artifact_output =~ "html"
assert artifact_output =~ "canonical"
@@ -47,15 +47,13 @@ defmodule LiveUi.Phase9IntegrationTest do
assert "--live-ui-foreground" in node.css_var_keys
end
- test "demo workbench surfaces browser-style details for reviewed examples" do
- assert {:ok, demo} = LiveUi.Demo.run(example: :canonical_styled_profile)
+ test "aligned example exports surface browser-style details for reviewed examples" do
+ assert {:ok, artifact_output} = LiveUi.Export.example(:button, :artifact)
- assert demo.view == :example
- assert demo.preview.mode == :html
- assert demo.preview.browser_style.realized_fields != []
- assert demo.preview.browser_style_nodes != []
- assert demo.html =~ "Browser Style Surface"
- assert demo.html =~ "Browser style nodes"
+ assert artifact_output =~ "browser_style_nodes"
+ assert artifact_output =~ "native"
+ assert artifact_output =~ "canonical"
+ assert artifact_output =~ "Button Canonical Review"
end
defp diagnostic_element do
diff --git a/packages/live_ui/test/live_ui/validation_test.exs b/packages/live_ui/test/live_ui/validation_test.exs
index 3a9615ff..1bfe9913 100644
--- a/packages/live_ui/test/live_ui/validation_test.exs
+++ b/packages/live_ui/test/live_ui/validation_test.exs
@@ -6,14 +6,20 @@ defmodule LiveUi.ValidationTest do
assert report.example_health.all_passing?
assert report.example_coverage.complete?
+ assert report.example_coverage.repository_example_ids == report.example_coverage.aligned_ids
+ assert report.example_coverage.missing_root_example_ids == []
+ assert report.example_coverage.unexpected_example_ids == []
assert report.continuity.aligned?
assert report.continuity.browser_style_aligned?
assert report.transport.sound?
assert report.runtime_authority.server_authoritative?
assert report.documentation_surface.complete?
+ assert report.documentation_surface.missing_snippets == %{}
+ assert report.documentation_surface.prohibited_mentions == []
assert report.release_readiness.ready?
- assert :paired_native_and_canonical_example_review in report.governance_gates.change_review_expectations
+ assert :aligned_focused_example_review in report.governance_gates.change_review_expectations
+ assert :canonical_review_on_aligned_ids in report.governance_gates.change_review_expectations
end
test "validation summary prints actionable release-readiness information" do
@@ -25,5 +31,7 @@ defmodule LiveUi.ValidationTest do
assert summary =~ "browser style aligned?: true"
assert summary =~ "documentation complete?: true"
assert summary =~ "release ready?: true"
+ assert summary =~ "missing root ids: []"
+ assert summary =~ "unexpected ids: []"
end
end
diff --git a/packages/live_ui/test/live_ui_test.exs b/packages/live_ui/test/live_ui_test.exs
index 0271efdf..91fd4832 100644
--- a/packages/live_ui/test/live_ui_test.exs
+++ b/packages/live_ui/test/live_ui_test.exs
@@ -2,7 +2,7 @@ defmodule LiveUiTest do
use ExUnit.Case, async: true
test "package reference exposes canonical areas" do
- assert [:widgets, :runtime, :renderer, :transport, :tooling, :styling, :demo] =
+ assert [:widgets, :runtime, :renderer, :transport, :tooling, :styling] =
LiveUi.package_areas()
assert %{
@@ -32,10 +32,6 @@ defmodule LiveUiTest do
assert LiveUi.examples() == LiveUi.Examples
end
- test "package exposes demo namespace" do
- assert LiveUi.demo() == LiveUi.Demo
- end
-
test "package exposes signal namespace" do
assert LiveUi.signals() == LiveUi.Signals
end
diff --git a/packages/live_ui/test/mix/tasks/live_ui_tasks_test.exs b/packages/live_ui/test/mix/tasks/live_ui_tasks_test.exs
index 5b38d243..c4e635c8 100644
--- a/packages/live_ui/test/mix/tasks/live_ui_tasks_test.exs
+++ b/packages/live_ui/test/mix/tasks/live_ui_tasks_test.exs
@@ -7,10 +7,10 @@ defmodule Mix.Tasks.LiveUiTasksTest do
html =
capture_io(fn ->
Mix.Task.reenable("live_ui.preview")
- Mix.Tasks.LiveUi.Preview.run(["native_styled_profile", "--format", "html"])
+ Mix.Tasks.LiveUi.Preview.run(["button", "--format", "html"])
end)
- assert html =~ "data-live-ui-widget=\"box\""
+ assert html =~ "data-live-ui-widget=\"button\""
catalog =
capture_io(fn ->
@@ -18,77 +18,31 @@ defmodule Mix.Tasks.LiveUiTasksTest do
Mix.Tasks.LiveUi.Preview.run([])
end)
- assert catalog =~ "native_display"
- assert catalog =~ "styled_continuity_compare"
+ assert catalog =~ ":button"
+ assert catalog =~ ":table"
end
- test "demo task prints summary and html output" do
- summary =
- capture_io(fn ->
- Mix.Task.reenable("live_ui.demo")
- Mix.Tasks.LiveUi.Demo.run([])
- end)
-
- assert summary =~ "LiveUi demo summary"
- assert summary =~ "view: home"
- assert summary =~ "category: foundational"
- assert summary =~ "widgets in category: 12"
-
- html =
- capture_io(fn ->
- Mix.Task.reenable("live_ui.demo")
- Mix.Tasks.LiveUi.Demo.run(["button", "--format", "html"])
- end)
-
- assert html =~ "Button"
- assert html =~ "data-live-ui-runtime=\"screen\""
- assert html =~ "data-live-ui-widget=\"button\""
- end
-
- test "demo task can launch the browser host mode" do
- port = free_port()
-
- output =
- capture_io(fn ->
- Mix.Task.reenable("live_ui.demo")
-
- Mix.Tasks.LiveUi.Demo.run([
- "--serve",
- "--port",
- Integer.to_string(port),
- "--linger-ms",
- "10"
- ])
- end)
-
- assert output =~ "LiveUi demo server"
- assert output =~ "http://127.0.0.1:#{port}/"
+ test "demo task is retired from the public tooling surface" do
+ refute Code.ensure_loaded?(Mix.Tasks.LiveUi.Demo)
end
test "inspect and export tasks print comparison-oriented maintainer output" do
inspection =
capture_io(fn ->
Mix.Task.reenable("live_ui.inspect")
- Mix.Tasks.LiveUi.Inspect.run(["native_styled_operations", "--format", "comparison"])
+ Mix.Tasks.LiveUi.Inspect.run(["button", "--format", "comparison"])
end)
- assert inspection =~ "canonical_styled_operations"
- assert inspection =~ "widgets_aligned?"
+ assert inspection =~ "Button Canonical Review"
+ assert inspection =~ "shared_widgets"
export =
capture_io(fn ->
Mix.Task.reenable("live_ui.export")
- Mix.Tasks.LiveUi.Export.run(["native_styled_profile", "--format", "metadata"])
+ Mix.Tasks.LiveUi.Export.run(["button", "--format", "metadata"])
end)
assert export =~ "review_artifact"
- assert export =~ "native_styled_profile"
- end
-
- defp free_port do
- {:ok, socket} = :gen_tcp.listen(0, [:binary, active: false, packet: :raw, reuseaddr: true])
- {:ok, port} = :inet.port(socket)
- :ok = :gen_tcp.close(socket)
- port
+ assert export =~ "live_ui/examples/button"
end
end