Skip to content

Normalize persisted render surfaces - #15

Merged
andycamara merged 1 commit into
mainfrom
feature/render-v2-normalization
Aug 2, 2026
Merged

andycamara merged 1 commit into
mainfrom
feature/render-v2-normalization

Conversation

@andycamara

@andycamara andycamara commented Aug 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • port the corrected Phase 3 canonical Page, Layout, and Component normalization contract
  • enforce normalization at shared storage boundaries and direct maintenance writers
  • add safe Astro action error transport and component pack semantic-version handling
  • consolidate the original Phase 3 implementation and its correction pass so OSS never lands the known intermediate gaps

Validation

  • rendering foundation: 32 passed, 6 expected parity failures in Node; 32 passed in workerd
  • 116 focused action, library, writer, SQLite, and Cloudflare storage tests
  • rendering writer and portability boundary checks passed
  • Astro check: 0 errors

No release, deployment, database migration execution, or bulk row rewrite is included.

Summary by CodeRabbit

  • New Features
    • Added consistent validation and normalization for pages, layouts, components, and inserted content.
    • Added clearer handling for invalid render input with safe, user-facing error messages.
    • Library installations now track component pack versions and indicate when updates are available.
  • Bug Fixes
    • Prevented invalid or unsupported content from being saved or persisted.
    • Improved compatibility with legacy styling and media properties.
    • Improved error decoding across saving, publishing, agent tools, and library workflows.
  • Tests
    • Expanded coverage for validation, persistence, error handling, and pack-version behavior.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds canonical render-surface preflight and normalization for actions, storage, seeds, and rendering. It adds safe RENDER_INPUT_INVALID errors, pack-version tracking, semantic source hashes, writer-boundary checks, and broad regression coverage.

Changes

Canonical rendering pipeline

Layer / File(s) Summary
Rendering contract and normalization
aria/lib/rendering/canonical/*, aria/lib/rendering/actionErrorMessage.ts, aria/lib/blocks/*
Editable surfaces and nodes now use canonical preflight, normalization, stable serialization, hashing, and legacy compatibility conversion.
Action preflight and persistence integration
aria/actions/*, aria/lib/storage/internal/domains/*
Save, CRUD, node, and library actions normalize inputs before persistence. Storage compares semantic source hashes and stores normalized DSL.
Seed and bootstrap writers
aria/lib/storage/sqlitePlatform.ts, aria/scripts/*
Seed and bootstrap writers normalize page, layout, and component versions and persist content hashes.
Error transport and agent tools
aria/admin/composables/*, aria/admin/features/Agent/*, aria/admin/lib/actions/actionResult.ts
Client and agent paths decode render-action errors and preserve RENDER_INPUT_INVALID.
Validation and regression coverage
aria/tests/*, package.json, vitest.rendering-foundation.*.config.ts
Tests cover normalization limits, invalid graphs, persistence, pack versions, error decoding, and rendering-writer boundaries.

Estimated code review effort: 5 (Critical) | ~120 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.58% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: normalization of persisted render surfaces.
Description check ✅ Passed The description explains the main changes and validation results, but it omits the template's checklist, architecture, security, and screenshots sections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/render-v2-normalization

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@andycamara
andycamara merged commit c3a6d37 into main Aug 2, 2026
3 of 4 checks passed
@andycamara
andycamara deleted the feature/render-v2-normalization branch August 2, 2026 19:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (7)
aria/actions/nodes.ts (1)

71-80: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

collectionSurfaceKind is defined three times. This PR adds the same collection-to-RenderSurfaceKind mapping in three separate files. The shared root cause is the absence of one exported helper next to the RenderSurfaceKind contract. If a fourth collection is added, or a mapping changes, all three copies must be updated together, and a missed copy produces a wrong surface kind in preflight.

  • aria/actions/nodes.ts#L71-L80: remove the local function and import the shared helper.
  • aria/admin/features/Agent/lib/tools/content/nodeWriteTools.ts#L45-L51: remove the local function and import the shared helper.
  • aria/admin/features/Agent/client/composables/useAgentClientTools.ts#L53-L59: remove the local function and import the shared helper.

Export the helper from aria/lib/rendering/canonical/contract.ts, where RenderSurfaceKind is declared, and type its parameter as the shared CollectionType.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aria/actions/nodes.ts` around lines 71 - 80, Centralize the
collection-to-surface mapping by exporting a helper next to RenderSurfaceKind in
aria/lib/rendering/canonical/contract.ts, with a CollectionType parameter.
Remove the local collectionSurfaceKind definitions and import the shared helper
in aria/actions/nodes.ts (lines 71-80),
aria/admin/features/Agent/lib/tools/content/nodeWriteTools.ts (lines 45-51), and
aria/admin/features/Agent/client/composables/useAgentClientTools.ts (lines
53-59).
aria/lib/migrations/propMigrations.ts (1)

108-111: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider keeping structured logging on this path.

migratePageDSL now runs inside normalizeEditableSurface, so this branch executes on the persistence path. console.error drops the stack trace and the structured context that the previous logger emitted. If the logger was removed for edge-runtime portability, state that reason in a short comment so the change is not reverted later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aria/lib/migrations/propMigrations.ts` around lines 108 - 111, Update the
migration error handling in migratePageDSL to preserve structured logging and
the error stack/context previously emitted by the logger, rather than using
console.error with only error.message. If console.error is required for
edge-runtime portability, add a brief comment documenting that constraint.
aria/lib/storage/internal/domains/dslAssets.ts (1)

187-232: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid recomputing existingVersionHash twice for the same row.

In saveLayoutDSL (Lines 193-198 and 212-217) and saveComponentDSL (Lines 477-482 and 496-501), resolveStoredSemanticSourceHash runs twice against the same existingVersionRow with identical inputs. The first call decides shouldInsertVersion. The second call, inside if (!shouldInsertVersion && existingVersionRow), recomputes the exact same value to decide whether to run the UPDATE.

Each call parses dsl_json and re-normalizes the surface, so this doubles that cost on every save that reuses an existing version (pinned or overwrite saves). Cache the first result and reuse it.

♻️ Proposed fix for the layout path (apply the same pattern to the component path)
       let shouldInsertVersion = true;
       const existingVersionRow = await context.getStoredVersionRow(
         "aria_layout_versions",
         id,
         version,
       );
+      let existingVersionHash: string | undefined;
       if (existingVersionRow) {
-        const existingVersionHash = await resolveStoredSemanticSourceHash({
+        existingVersionHash = await resolveStoredSemanticSourceHash({
           kind: "layout",
           row: existingVersionRow,
           fallback: () =>
             context.resolveStoredVersionContentHash(existingVersionRow),
         });
         if (existingVersionHash !== incomingHash) {
           if (!parsedOptions.overwriteVersionIfExists) {
             throw new Error(
               `Layout version conflict for ${id}: version ${version} already exists with different content`,
             );
           }
           shouldInsertVersion = false;
         } else {
           shouldInsertVersion = false;
         }
       }

       if (!shouldInsertVersion && existingVersionRow) {
-        const existingVersionHash = await resolveStoredSemanticSourceHash({
-          kind: "layout",
-          row: existingVersionRow,
-          fallback: () =>
-            context.resolveStoredVersionContentHash(existingVersionRow),
-        });
         if (existingVersionHash !== incomingHash) {

Also applies to: 471-516

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aria/lib/storage/internal/domains/dslAssets.ts` around lines 187 - 232, In
saveLayoutDSL and saveComponentDSL, cache the existingVersionHash returned by
the first resolveStoredSemanticSourceHash call for each existingVersionRow and
reuse it in the subsequent !shouldInsertVersion update check. Ensure the cached
value is available across both branches while preserving the existing conflict
and update behavior.
aria/tests/actions/save-action-nonce.test.ts (1)

2-2: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use astro:actions for the local test import.

Astro 7.1.3 exports ActionError from the public astro:actions virtual module, including its client runtime. Avoid importing from node_modules/astro/dist/actions/runtime/client.js, which is a published package internal build artifact.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aria/tests/actions/save-action-nonce.test.ts` at line 2, Update the import in
save-action-nonce.test.ts to import ActionError from Astro’s public
astro:actions virtual module instead of the internal node_modules runtime path,
preserving the existing ClientActionError alias.
aria/lib/storage/internal/domains/pageLifecycle.ts (1)

424-462: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Remove the duplicate semantic-hash computation for existingVersionRow.

resolveStoredSemanticSourceHash runs twice with the same arguments (kind: "page", row: existingVersionRow, same fallback): once at Line 431-436 to decide shouldInsertVersion, and again at Line 457-462 to decide whether to run the UPDATE. Each call re-parses dsl_json and re-runs normalization and hashing on the same row. Compute the hash once and reuse the result.

♻️ Proposed fix to compute the existing-version hash once
       let shouldInsertVersion = true;
       const existingVersionRow = await context.getStoredVersionRow(
         "aria_page_versions",
         id,
         version,
       );
+      let existingVersionHash: string | undefined;
       if (existingVersionRow) {
-        const existingVersionHash = await resolveStoredSemanticSourceHash({
+        existingVersionHash = await resolveStoredSemanticSourceHash({
           kind: "page",
           row: existingVersionRow,
           fallback: () =>
             context.resolveStoredVersionContentHash(existingVersionRow),
         });
         if (existingVersionHash !== incomingHash) {
           if (!parsedOptions.overwriteVersionIfExists) {
             throw new Error(
               `Page version conflict for ${id}: version ${version} already exists with different content`,
             );
           }
           shouldInsertVersion = false;
         } else {
           shouldInsertVersion = false;
         }
       }

       const versionedDSL = {
         ...normalizedDSL,
         version,
         updatedAt: now,
       };
       let committedWithGuard = false;

       if (!shouldInsertVersion && existingVersionRow) {
-        const existingVersionHash = await resolveStoredSemanticSourceHash({
-          kind: "page",
-          row: existingVersionRow,
-          fallback: () =>
-            context.resolveStoredVersionContentHash(existingVersionRow),
-        });
-        if (existingVersionHash !== incomingHash) {
+        if (existingVersionHash !== incomingHash) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aria/lib/storage/internal/domains/pageLifecycle.ts` around lines 424 - 462,
Compute the existing version’s semantic hash once in the version-check flow
around resolveStoredSemanticSourceHash, store it for reuse, and use that stored
value in the later !shouldInsertVersion update decision. Preserve the existing
conflict and overwrite behavior while eliminating the second call for the same
existingVersionRow.
aria/scripts/check-rendering-writer-boundaries.ts (1)

41-56: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

Tighten the boundary check beyond whole-file matching.

inspectRenderingWriterSource treats a file as safe if it contains a normalizer import and call ANYWHERE in the file, regardless of whether that normalizer actually guards the matched direct write. A file that already has one legitimate normalized writer (for example pageLifecycle.ts) would pass this check even if someone adds a second, unrelated, un-normalized dsl_json write to a different function in the same file. This leaves a gap in exactly the files most likely to receive new direct writers.

Consider narrowing the check to require the normalizer call to appear near each matched write (for example, within the same enclosing function, or within a fixed line-window before the write), rather than anywhere in the file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aria/scripts/check-rendering-writer-boundaries.ts` around lines 41 - 56,
Update inspectRenderingWriterSource so each direct dsl_json write matched by
VERSION_WRITE_RE and DSL_COLUMN_RE is validated against a nearby normalizer
call, rather than accepting any NORMALIZER_IMPORT_RE and NORMALIZER_CALL_RE
match anywhere in the file. Scope the association to the same enclosing function
or an equivalent bounded context, while preserving the existing safe result when
every matched write is guarded.
aria/lib/storage/internal/domains/surfaceNormalization.ts (1)

48-66: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Log the fallback path instead of swallowing every error silently.

The catch block returns input.fallback() for any failure, not just the "missing dsl_json" case. If normalizeSurfaceForPersistence throws for an unexpected reason (a bug in canonical normalization, an unexpected DSL shape), this silently degrades every stored-hash comparison to the legacy content-hash path with no signal. Add a log call before falling back so a normalization regression surfaces during change-detection instead of being hidden.

🔍 Proposed fix to surface unexpected normalization failures
   try {
     const dslJson = input.row.dsl_json ?? input.row.dslJson;
     if (typeof dslJson !== "string") {
       return input.fallback();
     }
     const normalized = await normalizeSurfaceForPersistence(
       input.kind,
       JSON.parse(dslJson),
     );
     return normalized.sourceHash;
-  } catch {
+  } catch (error) {
+    log("warn", "Falling back to legacy content hash", {
+      kind: input.kind,
+      error: error instanceof Error ? error.message : String(error),
+    });
     return input.fallback();
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aria/lib/storage/internal/domains/surfaceNormalization.ts` around lines 48 -
66, Update resolveStoredSemanticSourceHash so its catch block logs the
normalization or parsing failure before invoking input.fallback(). Include the
caught error and enough context to identify the failed stored semantic source
hash resolution, while preserving the existing fallback behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@aria/actions/library.ts`:
- Line 839: Update the install flow containing packVersion to source the
semantic version from the aria-free-pack entry in SEEDED_REGISTRY_MANIFEST when
available, rather than ARIA_FREE_BUTTON_COMPONENT.version. Preserve the existing
fallback behavior only when no matching manifest entry exists, and ensure the
value remains independent of the component storage revision.

In `@aria/actions/nodes.ts`:
- Line 455: Restore BuilderNodeSchema validation for the preflighted nodes in
the relevant action flow before normalization, using the parsed result as the
typed BuilderNode[] value instead of the unchecked cast near
normalizeNodeForPersist. Apply the same structural validation to the insertNode
path around its nodes schema, so invalid primitives or objects cannot reach
persistence.
- Around line 1092-1103: The replacement-node ID mismatch in the preflight
validation must throw RenderContractError rather than createError so safe
render-error handling can serialize it. Update the mismatch branch in the
preflightBuilderNodeInput flow to construct RenderContractError with
createRenderFailure and preserve the existing RENDER_INPUT_INVALID metadata.

In `@aria/admin/lib/actions/actionResult.ts`:
- Around line 49-54: Update the error-message selection in the visible
action-result helper so empty decoded messages and empty result.error.message
values are treated as missing, allowing fallbackMessage to be returned. Preserve
the existing precedence for non-empty decoded and transport error messages so
unwrapActionPayload continues receiving a meaningful error string.

In `@aria/lib/blocks/nodesToHtml.ts`:
- Around line 1229-1230: Update aria/lib/blocks/nodesToHtml.ts lines 1229-1230
in collectResponsiveStyleRules to resolve the tag with the same context-aware
logic as renderNode, including canonical icons emitting svg, before building
responsive selectors. Apply the identical tag resolution in
collectNodeStylesheet at lines 1317-1318 before collectNodeStylesheetRules.

In `@aria/lib/rendering/canonical/normalizeEditableSurface.ts`:
- Around line 456-483: Update the normalization flow around
restoreJsonValueFields and the options.freeze branch so restored source fields
are cloned before deepFreeze can reach them, preserving the caller-owned input
objects while retaining the normalized values and serialization behavior. Ensure
the cloned values cover every field restored by restoreJsonValueFields,
including props, a11y, frontmatter, structuredData, filter, fallback, and
overrides.

In `@aria/lib/schemas/nodes.ts`:
- Line 834: Update ComponentDSLSchema.packVersion to use the shared version
schema instead of z.string().optional(), and reuse that same schema for
PackManifestSchema.version. Preserve the repository’s x.y.z validation and
intended prerelease/build-version policy, keeping the field optional where
currently applicable.

In `@aria/tests/storage/cloudflare-storage.test.ts`:
- Around line 287-322: Update the no-rows-created assertions in
aria/tests/storage/cloudflare-storage.test.ts#L287-L322 and
aria/tests/storage/sqlite-storage.test.ts#L250-L281 to replace both hardcoded
'page-home' query values with bound samplePage.id parameters, while preserving
the existing COUNT(*) checks for aria_page_versions and aria_page_meta.

In `@package.json`:
- Line 105: Wire the package.json check:rendering-writers script into the
aggregate check commands, including check:push-guards and npm run check, and add
it to the relevant CI workflow check steps. Run it alongside the existing checks
so direct DSL writer boundary violations fail automated validation.

---

Nitpick comments:
In `@aria/actions/nodes.ts`:
- Around line 71-80: Centralize the collection-to-surface mapping by exporting a
helper next to RenderSurfaceKind in aria/lib/rendering/canonical/contract.ts,
with a CollectionType parameter. Remove the local collectionSurfaceKind
definitions and import the shared helper in aria/actions/nodes.ts (lines 71-80),
aria/admin/features/Agent/lib/tools/content/nodeWriteTools.ts (lines 45-51), and
aria/admin/features/Agent/client/composables/useAgentClientTools.ts (lines
53-59).

In `@aria/lib/migrations/propMigrations.ts`:
- Around line 108-111: Update the migration error handling in migratePageDSL to
preserve structured logging and the error stack/context previously emitted by
the logger, rather than using console.error with only error.message. If
console.error is required for edge-runtime portability, add a brief comment
documenting that constraint.

In `@aria/lib/storage/internal/domains/dslAssets.ts`:
- Around line 187-232: In saveLayoutDSL and saveComponentDSL, cache the
existingVersionHash returned by the first resolveStoredSemanticSourceHash call
for each existingVersionRow and reuse it in the subsequent !shouldInsertVersion
update check. Ensure the cached value is available across both branches while
preserving the existing conflict and update behavior.

In `@aria/lib/storage/internal/domains/pageLifecycle.ts`:
- Around line 424-462: Compute the existing version’s semantic hash once in the
version-check flow around resolveStoredSemanticSourceHash, store it for reuse,
and use that stored value in the later !shouldInsertVersion update decision.
Preserve the existing conflict and overwrite behavior while eliminating the
second call for the same existingVersionRow.

In `@aria/lib/storage/internal/domains/surfaceNormalization.ts`:
- Around line 48-66: Update resolveStoredSemanticSourceHash so its catch block
logs the normalization or parsing failure before invoking input.fallback().
Include the caught error and enough context to identify the failed stored
semantic source hash resolution, while preserving the existing fallback
behavior.

In `@aria/scripts/check-rendering-writer-boundaries.ts`:
- Around line 41-56: Update inspectRenderingWriterSource so each direct dsl_json
write matched by VERSION_WRITE_RE and DSL_COLUMN_RE is validated against a
nearby normalizer call, rather than accepting any NORMALIZER_IMPORT_RE and
NORMALIZER_CALL_RE match anywhere in the file. Scope the association to the same
enclosing function or an equivalent bounded context, while preserving the
existing safe result when every matched write is guarded.

In `@aria/tests/actions/save-action-nonce.test.ts`:
- Line 2: Update the import in save-action-nonce.test.ts to import ActionError
from Astro’s public astro:actions virtual module instead of the internal
node_modules runtime path, preserving the existing ClientActionError alias.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d68f518-f3c1-42de-94aa-747ff711ac41

📥 Commits

Reviewing files that changed from the base of the PR and between bd05b67 and f45eb66.

📒 Files selected for processing (49)
  • aria/actions/_renderContractError.ts
  • aria/actions/crud.ts
  • aria/actions/library.ts
  • aria/actions/nodes.ts
  • aria/actions/save.ts
  • aria/admin/composables/useActions.ts
  • aria/admin/features/Agent/client/composables/useAgentClientTools.ts
  • aria/admin/features/Agent/lib/schemas/toolProtocol.ts
  • aria/admin/features/Agent/lib/tools/content/nodeWriteTools.ts
  • aria/admin/features/Agent/lib/tools/toolErrors.ts
  • aria/admin/features/Core/composables/useSavePublish.ts
  • aria/admin/features/Studio/composer/composables/libraryActionResults.ts
  • aria/admin/lib/actions/actionResult.ts
  • aria/lib/blocks/nodesToAstro.ts
  • aria/lib/blocks/nodesToHtml.ts
  • aria/lib/migrations/propMigrations.ts
  • aria/lib/rendering/actionErrorMessage.ts
  • aria/lib/rendering/canonical/contract.ts
  • aria/lib/rendering/canonical/index.ts
  • aria/lib/rendering/canonical/legacyNodeCompatibility.ts
  • aria/lib/rendering/canonical/normalizeEditableSurface.ts
  • aria/lib/rendering/canonical/preflight.ts
  • aria/lib/rendering/canonical/stableJson.ts
  • aria/lib/schemas/nodes.ts
  • aria/lib/storage/internal/domains/dslAssets.ts
  • aria/lib/storage/internal/domains/pageLifecycle.ts
  • aria/lib/storage/internal/domains/surfaceNormalization.ts
  • aria/lib/storage/sqlitePlatform.ts
  • aria/lib/types/nodes.ts
  • aria/scripts/bootstrap-remote-storage.ts
  • aria/scripts/check-rendering-writer-boundaries.ts
  • aria/scripts/cleanup-navigation-bindings.ts
  • aria/scripts/migrate-layout-slot-defaults.ts
  • aria/scripts/seed-d1-storage.ts
  • aria/scripts/seed-header-01.ts
  • aria/tests/actions/library-pack-version.test.ts
  • aria/tests/actions/save-action-nonce.test.ts
  • aria/tests/composables/actionResult.test.ts
  • aria/tests/composables/libraryActionResults.test.ts
  • aria/tests/features/agent/tools/tool-errors.test.ts
  • aria/tests/icons/action-handlers.integration.test.ts
  • aria/tests/lib/nodesToHtml.test.ts
  • aria/tests/rendering-foundation/normalization.parity.test.ts
  • aria/tests/scripts/rendering-writer-boundaries.test.ts
  • aria/tests/storage/cloudflare-storage.test.ts
  • aria/tests/storage/sqlite-storage.test.ts
  • package.json
  • vitest.rendering-foundation.node.config.ts
  • vitest.rendering-foundation.workerd.config.ts
💤 Files with no reviewable changes (1)
  • aria/tests/lib/nodesToHtml.test.ts

Comment thread aria/actions/library.ts
...ARIA_FREE_BUTTON_COMPONENT,
source: "aria" as const,
packId: "aria-free-pack",
packVersion: ARIA_FREE_BUTTON_COMPONENT.version ?? "1.0.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

packVersion is sourced from the component storage revision.

ARIA_FREE_BUTTON_COMPONENT.version is the storage revision field. ComponentDSL documents packVersion as the "Installed pack semantic version; independent of storage revision". Every other install path sets packVersion from pack.version in the manifest.

If version is a revision counter such as "3", parseVersion returns null, resolveInstalledPackVersion returns UNKNOWN_PACK_VERSION, and checkUpdates at line 948 then reports a permanent update for the starter pack. Use a semantic pack version instead.

🐛 Proposed fix
-        packVersion: ARIA_FREE_BUTTON_COMPONENT.version ?? "1.0.0",
+        packVersion: "1.0.0",

Prefer the manifest entry for aria-free-pack if one exists in SEEDED_REGISTRY_MANIFEST.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
packVersion: ARIA_FREE_BUTTON_COMPONENT.version ?? "1.0.0",
packVersion: "1.0.0",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aria/actions/library.ts` at line 839, Update the install flow containing
packVersion to source the semantic version from the aria-free-pack entry in
SEEDED_REGISTRY_MANIFEST when available, rather than
ARIA_FREE_BUTTON_COMPONENT.version. Preserve the existing fallback behavior only
when no matching manifest entry exists, and ensure the value remains independent
of the component storage revision.

Comment thread aria/actions/nodes.ts
id: IdSchema,
parentId: z.string().nullable(),
nodes: z.array(BuilderNodeSchema).min(1),
nodes: z.array(z.unknown()).min(1),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Restore structural node validation after preflight.

nodes accepted BuilderNodeSchema before this change. It now accepts z.array(z.unknown()). Line 924 makes the same change for insertNode.

preflightBuilderNodeArrayInput does not replace that validation. It validates the JSON graph only: prototypes, accessors, cycles, size, depth, and value types. It accepts a plain string, a number, or an object with no id, type, props, styles, or children. The result is then cast at line 656 with as BuilderNode[], which the compiler cannot check.

A request with nodes: ["x"] therefore reaches normalizeNodeForPersist at line 658 with a primitive, and nodeIds at line 698 resolves to undefined. Non-node data can be persisted into doc.nodes.

Parse the preflighted source with BuilderNodeSchema before normalization, and remove the unchecked cast.

🛡️ Proposed fix for the insert paths
-    const preflightedNodes = preflightBuilderNodeArrayInput({
-      kind: collectionSurfaceKind(input.collection),
-      nodes: input.nodes,
-    }).source as BuilderNode[];
-    const normalizedNodes = preflightedNodes.map((node) =>
+    const preflightedNodes = z
+      .array(BuilderNodeSchema)
+      .parse(
+        preflightBuilderNodeArrayInput({
+          kind: collectionSurfaceKind(input.collection),
+          nodes: input.nodes,
+        }).source,
+      );
+    const normalizedNodes = preflightedNodes.map((node) =>
       normalizeNodeForPersist({
         ...node,
         id: node.id || generateNodeId(),
       } as BuilderNode),
     );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
nodes: z.array(z.unknown()).min(1),
const preflightedNodes = z
.array(BuilderNodeSchema)
.parse(
preflightBuilderNodeArrayInput({
kind: collectionSurfaceKind(input.collection),
nodes: input.nodes,
}).source,
);
const normalizedNodes = preflightedNodes.map((node) =>
normalizeNodeForPersist({
...node,
id: node.id || generateNodeId(),
} as BuilderNode),
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aria/actions/nodes.ts` at line 455, Restore BuilderNodeSchema validation for
the preflighted nodes in the relevant action flow before normalization, using
the parsed result as the typed BuilderNode[] value instead of the unchecked cast
near normalizeNodeForPersist. Apply the same structural validation to the
insertNode path around its nodes schema, so invalid primitives or objects cannot
reach persistence.

Comment thread aria/actions/nodes.ts
Comment on lines +1092 to +1103
if (preflightedReplacement.id !== input.nodeId) {
throw createError(
"RENDER_INPUT_INVALID",
"The render input is invalid.",
{
surfaceKind: collectionSurfaceKind(input.collection),
stage: "preflight",
issue: "replacement-node-id-mismatch",
issueCount: 1,
},
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check what toSafeRenderContractActionError matches and the encoded message constant.
set -euo pipefail

fd -t f '_renderContractError.ts' aria/actions --exec cat -n {}
fd -t f 'actionErrorMessage.ts' aria/lib/rendering --exec cat -n {}
rg -nP --type=ts -C3 'export function createError' aria/lib aria/actions

Repository: ariabuilder/aria

Length of output: 2981


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## nodes.ts replaceNode section"
sed -n '1040,1125p' aria/actions/nodes.ts | cat -n | sed 's/^/1040-1125: /'

echo
echo "## _shared createError implementation"
sed -n '280,360p' aria/actions/_shared.ts | cat -n | sed 's/^/280-360: /'

echo
echo "## canonical render contract errors"
fd -t f 'canonical.ts' aria/lib/rendering --exec rg -n -C4 'class RenderContractError|RenderContractError|createRenderFailure|RENDER_INPUT_INVALID' {}

echo
echo "## preflight RENDER_INPUT_INVALID usage"
fd -t f 'preflight.ts' aria/lib/rendering --exec ast-grep outline {} --view compact | sed -n '1,200p'
fd -t f 'preflight.ts' aria/lib/rendering --exec rg -n -C4 'createRenderFailure|RENDER_INPUT_INVALID|render input' {}

Repository: ariabuilder/aria

Length of output: 8100


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## createError implementation"
sed -n '320,355p' aria/actions/_shared.ts | cat -n | sed 's/^/320-355: /'

echo
echo "## canonical render contract errors"
fd -t f 'canonical.ts' aria/lib/rendering --exec sh -c 'echo "-- {}"; sed -n "1,220p" "{}" | cat -n'

echo
echo "## render contract error behavior probe from source"
python3 - <<'PY'
from pathlib import Path
p = Path('aria/actions/_shared.ts')
s = p.read_text()
start = s.find('export function createError(')
end = s.find('\nexport function isActionError(', start)
print(s[start:end])
PY

Repository: ariabuilder/aria

Length of output: 2048


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## aria/actions/nodes.ts imports and relevant constants"
sed -n '1,80p' aria/actions/nodes.ts | cat -n | sed 's/^/1-80: /'

echo
echo "## handleError continuation"
sed -n '1135,1175p' aria/actions/nodes.ts | cat -n | sed 's/^/1135-1175: /'

echo
echo "## RenderContractError definitions and createRenderFailure usages"
rg -n -C5 'class RenderContractError|export .*RenderContractError|createRenderFailure|RENDER_INPUT_INVALID' aria/lib/rendering aria/actions

echo
echo "## precise code probe: toSafeRenderContractActionError and createError/decode message shapes"
sed -n '13,23p' aria/actions/_renderContractError.ts | cat -n | sed 's/^/13-23: /'
sed -n '327,343p' aria/actions/_shared.ts | cat -n | sed 's/^/327-343: /'
sed -n '15,24p' aria/lib/rendering/actionErrorMessage.ts | cat -n | sed 's/^/15-24: /'
python3 - <<'PY'
from pathlib import Path
src = Path('aria/actions/_shared.ts').read_text()
start = src.find('export function createError(')
end = src.find('\nexport function isActionError(', start+1)
code = src[start:end]
print(code)
print("returns structured object:", "ActionError = {" in code or "return {" in code)
PY

Repository: ariabuilder/aria

Length of output: 25179


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## handleError full"
sed -n '360,415p' aria/actions/_shared.ts | cat -n | sed 's/^/360-415: /'

Repository: ariabuilder/aria

Length of output: 2570


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## imports around createError"
rg -n 'CREATE.*ERROR|ERROR_CODES|from.*_shared|createError\(' aria/actions/nodes.ts | sed -n '1,60p'

echo
echo "## create error enum definitions"
rg -n -C3 'const ERROR_CODES|export const ERROR_CODES|INVALID_INPUT|NODE_NOT_FOUND|RENDER_INPUT_INVALID' aria/actions/_shared.ts \
  aria/actions/nodes.ts \
  aria/actions/library.ts \
  aria/lib/rendering/canonical \
  aria/lib/rendering/actionErrorMessage.ts

Repository: ariabuilder/aria

Length of output: 15962


Throw RenderContractError for the ID mismatch instead of createError.

toSafeRenderContractActionError only accepts RenderContractError; this code returns a structured action error, so it is not serialized by safe render-error handling. Use the existing preflightBuilderNodeInput rejection pattern, e.g. new RenderContractError(createRenderFailure("RENDER_INPUT_INVALID", { surfaceKind, stage: "preflight", issue: "replacement-node-id-mismatch", issueCount: 1 })).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aria/actions/nodes.ts` around lines 1092 - 1103, The replacement-node ID
mismatch in the preflight validation must throw RenderContractError rather than
createError so safe render-error handling can serialize it. Update the mismatch
branch in the preflightBuilderNodeInput flow to construct RenderContractError
with createRenderFailure and preserve the existing RENDER_INPUT_INVALID
metadata.

Comment on lines +49 to 54
return (
decodeRenderActionErrorMessage(result.error.message)?.message ??
result.error.message ??
fallbackMessage
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

An empty error.message bypasses the fallback and suppresses the error.

?? only replaces null and undefined. If the transport returns error: { message: "" }, this function returns "". unwrapActionPayload at line 93 then treats the result as non-errored and continues to parse result.data. Use a non-empty check.

🐛 Proposed fix
-  return (
-    decodeRenderActionErrorMessage(result.error.message)?.message ??
-    result.error.message ??
-    fallbackMessage
-  );
+  const decoded = decodeRenderActionErrorMessage(result.error.message);
+  if (decoded) return decoded.message;
+  const message = result.error.message;
+  return typeof message === "string" && message.trim().length > 0
+    ? message
+    : fallbackMessage;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aria/admin/lib/actions/actionResult.ts` around lines 49 - 54, Update the
error-message selection in the visible action-result helper so empty decoded
messages and empty result.error.message values are treated as missing, allowing
fallbackMessage to be returned. Preserve the existing precedence for non-empty
decoded and transport error messages so unwrapActionPayload continues receiving
a meaningful error string.

Comment on lines 1229 to +1230
if (Object.keys(renderStyles).length > 0) {
const tag = getRenderedStyleTag(node, renderProps);
const tag = getNativeTagForRenderableNode(node, renderProps) ?? "div";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

CSS selectors can target a different tag than the renderer emits in aria/lib/blocks/nodesToHtml.ts. The removal of getRenderedStyleTag is the shared root cause. Both style collectors now resolve the tag with getNativeTagForRenderableNode(node, renderProps) ?? "div", while renderNode uses getNativeTagForRenderableNodeInContext and emits an inline <svg> for canonical icons. getResponsiveStyleSelector builds the selector from that tag, so the generated rules can fail to match the emitted element.

  • aria/lib/blocks/nodesToHtml.ts#L1229-L1230: resolve the tag in collectResponsiveStyleRules with the same context-aware logic as renderNode, including the canonical-icon svg case.
  • aria/lib/blocks/nodesToHtml.ts#L1317-L1318: apply the same tag resolution in collectNodeStylesheet before calling collectNodeStylesheetRules.
📍 Affects 1 file
  • aria/lib/blocks/nodesToHtml.ts#L1229-L1230 (this comment)
  • aria/lib/blocks/nodesToHtml.ts#L1317-L1318
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aria/lib/blocks/nodesToHtml.ts` around lines 1229 - 1230, Update
aria/lib/blocks/nodesToHtml.ts lines 1229-1230 in collectResponsiveStyleRules to
resolve the tag with the same context-aware logic as renderNode, including
canonical icons emitting svg, before building responsive selectors. Apply the
identical tag resolution in collectNodeStylesheet at lines 1317-1318 before
collectNodeStylesheetRules.

Comment on lines +456 to +483
const parsed = applyAuthoritativeSchema(input.kind, source);
restoreJsonValueFields(source, parsed as unknown as Record<string, unknown>);
assertPortableObjectPrototypes(input.kind, parsed);
removeUndefinedObjectProperties(parsed);
const serialized = stableSerializeJson(parsed);
const canonicalSourceBytes = new TextEncoder().encode(serialized).byteLength;
if (canonicalSourceBytes > MAX_CANONICAL_SOURCE_BYTES) {
throw new RenderContractError(
createRenderFailure("RENDER_INPUT_INVALID", {
surfaceKind: input.kind,
stage: "canonical-size",
issue: "source-size",
violatedLimit: "canonicalSourceBytes",
maximum: MAX_CANONICAL_SOURCE_BYTES,
actual: canonicalSourceBytes,
issueCount: 1,
}),
);
}
const normalized: NormalizedRenderSurface<K> = {
contractVersion: 1,
kind: input.kind,
source: parsed,
sourceHash: await sha256Text(serialized),
};

return options.freeze === true ? deepFreeze(normalized) : normalized;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether preflightEditableSurface deep-clones the input source.
set -euo pipefail

rg -n -C 5 'export function preflightEditableSurface' aria/lib/rendering/canonical/preflight.ts
rg -n -C 3 'structuredClone|JSON\.parse\(JSON\.stringify' aria/lib/rendering/canonical/preflight.ts
rg -n -C 3 'freeze: *true' aria/lib aria/actions aria/scripts

Repository: ariabuilder/aria

Length of output: 448


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- relevant normalizeEditableSurface section ---\n'
sed -n '220,490p' aria/lib/rendering/canonical/normalizeEditableSurface.ts | cat -n

printf '\n--- preflightEditableSurface and graph prefabs ---\n'
sed -n '330,390p' aria/lib/rendering/canonical/preflight.ts | cat -n
rg -n -C 4 'function preflightJsonGraph|structuredClone|JSON\.parse\(JSON\.stringify|deepFreeze|freeze' aria/lib/rendering/canonical/preflight.ts aria/lib/rendering/canonical/normalizeEditableSurface.ts

printf '\n--- normalizeSurfaceNodeTrees references ---\n'
rg -n -C 5 'normalizeSurfaceNodeTrees|preflightEditableSurface|options\.freeze' aria/lib/rendering aria/actions aria/scripts || true

Repository: ariabuilder/aria

Length of output: 245


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- import/export outline for canonical files ---\n'
ast-grep outline aria/lib/rendering/canonical/preflight.ts --view compact || true
ast-grep outline aria/lib/rendering/canonical/normalizeEditableSurface.ts --view compact || true

printf '\n--- deepFreeze implementation usages ---\n'
fd -a . aria/lib | rg 'deepFreeze|freeze' || true
rg -n -C 4 'function deepFreeze|const deepFreeze|export .*deepFreeze' aria/lib aria/actions aria/scripts || true

Repository: ariabuilder/aria

Length of output: 245


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- relevant normalizeEditableSurface section ---'
sed -n '220,490p' aria/lib/rendering/canonical/normalizeEditableSurface.ts

echo
echo '--- preflightEditableSurface and graph prefabs ---'
sed -n '330,390p' aria/lib/rendering/canonical/preflight.ts
rg -n -C 4 'function preflightJsonGraph|structuredClone|JSON\.parse|(?:JSON\.stringify)|deepFreeze|freeze' aria/lib/rendering/canonical/preflight.ts aria/lib/rendering/canonical/normalizeEditableSurface.ts

echo
echo '--- normalizeSurfaceNodeTrees references ---'
rg -n -C 5 'normalizeSurfaceNodeTrees|preflightEditableSurface|options\.freeze' aria/lib/rendering aria/actions aria/scripts || true

echo
echo '--- deepFreeze implementation/usages ---'
rg -n -C 4 'function deepFreeze|const deepFreeze|export .*deepFreeze|deepFreeze\(' aria/lib aria/actions aria/scripts || true

Repository: ariabuilder/aria

Length of output: 20260


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- preflightJsonGraph body ---'
sed -n '112,220p' aria/lib/rendering/canonical/preflight.ts

echo
echo '--- normalizeSurfaceNodeTrees / normalizeNodeTree bodies ---'
sed -n '150,195p' aria/lib/rendering/canonical/normalizeEditableSurface.ts

echo
echo '--- shallow-copy/freezing behavior probe ---'
node - <<'JS'
function normalizeSurfaceNodeTrees(source) {
  const normalized = { ...source };
  return normalized;
}
function applyAuthoritativeSchema(source) {
  return JSON.parse(JSON.stringify(source));
}
function restoreJsonValueFields(source, parsed) {
  parsed.nested = source.nested;
  return { serialized: JSON.stringify(parsed) };
}
function deepFreeze(value) {
  const stack = [];
  if (value && typeof value === "object") stack.push(value);
  while (stack.length > 0) {
    const current = stack.pop();
    if (!current || Object.isFrozen(current)) continue;
    for (const child of Object.values(current)) {
      if (child && typeof child === "object" && !Object.isFrozen(child)) stack.push(child);
    }
    Object.freeze(current);
  }
  return value;
}
const input = { source: { root: "x", nested: { value: 1 } } };
const preflight = { source: input.source };
let source = normalizeSurfaceNodeTrees(preflight.source);
const parsed = applyAuthoritativeSchema(source);
const serialized = restoreJsonValueFields(source, parsed).serialized;
const normalized = { kind: "page", source: parsed };
deepFreeze(normalized);
source.root = "z";
try {
  source.nested.value = 2;
  console.log("sloppy write succeeded, nested.value =", source.nested.value, "parsed.nested.value =", parsed.nested.value);
} catch (e) {
  console.log("sloppy write threw:", e.name + ": " + e.message);
}
try {
  "use strict";
  parsed.nested.value = 3;
  console.log("strict write succeeded, parsed.nested.value =", parsed.nested.value);
} catch (e) {
  console.log("strict write threw:", e.name + ": " + e.message);
}
console.log("normalized source frozen:", Object.isFrozen(normalized.source), "parsed.nested frozen:", Object.isFrozen(normalized.source.nested));
JS

Repository: ariabuilder/aria

Length of output: 5252


Preserve the caller’s source when freezing canonical normalized surfaces.

restoreJsonValueFields copies props, a11y, frontmatter, structuredData, filter, fallback, and overrides by reference into parsed. When options.freeze === true, deepFreeze(normalized) freezes those caller-owned objects. A later write can fail silently in sloppy mode or throw in strict mode, so clone the restored values before freezing or document that the caller must not mutate the original surface after normalization is called with freeze: true.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aria/lib/rendering/canonical/normalizeEditableSurface.ts` around lines 456 -
483, Update the normalization flow around restoreJsonValueFields and the
options.freeze branch so restored source fields are cloned before deepFreeze can
reach them, preserving the caller-owned input objects while retaining the
normalized values and serialization behavior. Ensure the cloned values cover
every field restored by restoreJsonValueFields, including props, a11y,
frontmatter, structuredData, filter, fallback, and overrides.

Comment thread aria/lib/schemas/nodes.ts

source: ComponentSourceSchema.optional(), // Defaults to 'custom' for existing components
packId: z.string().optional(), // Pack identifier for aria-sourced components
packVersion: z.string().optional(), // Installed pack version, not storage revision

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate packVersion with the pack version schema.

Line [834] accepts any string, including "" and "latest". PackManifestSchema.version at Lines [898]-[900] enforces the repository’s x.y.z version contract. Since ComponentDSLSchema is used by authoritative normalization, invalid pack metadata can be persisted.

Reuse one shared version schema for packVersion and PackManifestSchema.version, including the intended prerelease/build-version policy.

Proposed fix
-    packVersion: z.string().optional(), // Installed pack version, not storage revision
+    packVersion: PackVersionSchema.optional(), // Installed pack version, not storage revision
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aria/lib/schemas/nodes.ts` at line 834, Update ComponentDSLSchema.packVersion
to use the shared version schema instead of z.string().optional(), and reuse
that same schema for PackManifestSchema.version. Preserve the repository’s x.y.z
validation and intended prerelease/build-version policy, keeping the field
optional where currently applicable.

Comment on lines +287 to +322
it("rejects invalid recursive page input before creating D1 rows", async () => {
const adapter = new CloudflareStorageAdapter({
aria_db: createD1Mock(client) as any,
});
const cyclicNode: Record<string, unknown> = {
id: "cycle",
type: "Container",
props: {},
styles: {},
};
cyclicNode.children = [cyclicNode];

await expect(
adapter.savePageDSL(samplePage.id, {
...samplePage,
nodes: [cyclicNode],
} as unknown as PageDSL),
).rejects.toMatchObject({
failure: {
code: "RENDER_INPUT_INVALID",
context: expect.objectContaining({ stage: "preflight" }),
},
});

const [versions, metadata] = await Promise.all([
client.execute(
"SELECT COUNT(*) AS count FROM aria_page_versions WHERE id = 'page-home'",
),
client.execute(
"SELECT COUNT(*) AS count FROM aria_page_meta WHERE id = 'page-home'",
),
]);
expect(Number(versions.rows[0]?.count)).toBe(0);
expect(Number(metadata.rows[0]?.count)).toBe(0);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate test files =="
git ls-files | rg 'api/tests/storage/(cloudflare|sqlite)-storage\.test\.ts$' || true

echo
echo "== Cloudflare test relevant section =="
if [ -f aria/tests/storage/cloudflare-storage.test.ts ]; then
  wc -l aria/tests/storage/cloudflare-storage.test.ts
  sed -n '1,80p;260,330p' aria/tests/storage/cloudflare-storage.test.ts | cat -n
fi

echo
echo "== SQLite test relevant section =="
if [ -f aria/tests/storage/sqlite-storage.test.ts ]; then
  wc -l aria/tests/storage/sqlite-storage.test.ts
  sed -n '1,80p;230,290p' aria/tests/storage/sqlite-storage.test.ts | cat -n
fi

echo
echo "== Search for samplePage id definition/usages =="
rg -n "samplePage|page-home|savePageDSL|COUNT\\(\\*\\)" aria/tests/storage -S

Repository: ariabuilder/aria

Length of output: 46516


Query by samplePage.id instead of the hardcoded 'page-home' literal in the no-rows-created assertions.

Both tests call adapter.savePageDSL(samplePage.id, ...) but check aria_page_versions and aria_page_meta with the literal 'page-home'. Use samplePage.id as a parameter so later changes to the test fixture do not make this assertion check an unrelated page id.

  • aria/tests/storage/cloudflare-storage.test.ts#L287-L322: replace the two 'page-home' literals in the COUNT(*) queries with a bound samplePage.id.
  • aria/tests/storage/sqlite-storage.test.ts#L250-L281: replace the two 'page-home' literals in the COUNT(*) queries with a bound samplePage.id.
📍 Affects 2 files
  • aria/tests/storage/cloudflare-storage.test.ts#L287-L322 (this comment)
  • aria/tests/storage/sqlite-storage.test.ts#L250-L281
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@aria/tests/storage/cloudflare-storage.test.ts` around lines 287 - 322, Update
the no-rows-created assertions in
aria/tests/storage/cloudflare-storage.test.ts#L287-L322 and
aria/tests/storage/sqlite-storage.test.ts#L250-L281 to replace both hardcoded
'page-home' query values with bound samplePage.id parameters, while preserving
the existing COUNT(*) checks for aria_page_versions and aria_page_meta.

Comment thread package.json
"test:rendering:canvas-parity": "playwright test --config playwright.rendering-parity.config.ts",
"test:runtime-stability:workerd": "vitest run --config vitest.runtime-stability.workerd.config.ts",
"check:rendering-boundaries": "node --import tsx aria/scripts/check-rendering-foundation-boundaries.ts",
"check:rendering-writers": "node --import tsx aria/scripts/check-rendering-writer-boundaries.ts",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Confirm check:rendering-writers is wired into an aggregate script or CI workflow.
rg -n 'check:rendering-writers' package.json
rg -n --type=yaml 'check:rendering-writers|npm run check|pnpm (run )?check' .github/workflows 2>/dev/null

Repository: ariabuilder/aria

Length of output: 522


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- package.json scripts context ---'
sed -n '90,115p' package.json

echo '--- CI workflow relevant context ---'
sed -n '30,55p' .github/workflows/ci.yml

echo '--- publish workflow relevant context ---'
sed -n '35,50p' .github/workflows/publish-npm.yml

echo '--- check scripts references in package scripts ---'
python3 - <<'PY'
import json
with open('package.json') as f:
    data=json.load(f)
for name,cmd in data.get('scripts',{}).items():
    if 'check' in name or 'check:' in str(cmd):
        print(f'{name}: {cmd}')
PY

Repository: ariabuilder/aria

Length of output: 4275


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- locate project-command.ts ---'
fd -a 'project-command\.ts$' .

echo '--- project command definitions ---'
PROJECT_CMD=$(fd 'project-command\.ts$' . | head -n 1)
if [ -n "${PROJECT_CMD:-}" ]; then
  wc -l "$PROJECT_CMD"
  rg -n "check|rendering-writers|rendering.*writer|command|check:" "$PROJECT_CMD" -C 3
fi

echo '--- all references to script name/project command check in repo ---'
rg -n 'check:rendering-writers|project-command\.ts check|rendering.*writers|dsl_json|writer.*normaliz|normaliz.*writer' .

Repository: ariabuilder/aria

Length of output: 16294


Wire check:rendering-writers into aggregate checks.

check:rendering-writers is not called from check:push-guards, npm run check, or the CI workflows. Call it before/after the existing checks so un-normalized direct DSL writers fail CI instead of remaining manual-only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` at line 105, Wire the package.json check:rendering-writers
script into the aggregate check commands, including check:push-guards and npm
run check, and add it to the relevant CI workflow check steps. Run it alongside
the existing checks so direct DSL writer boundary violations fail automated
validation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant