@happyvertical/smrt-content 0.55.21 → 0.55.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/agents/content-list.md +8 -0
  2. package/dist/chunks/{content-query-2K5cNRBR.js → content-query-I4DltOFE.js} +43 -5
  3. package/dist/chunks/{content-query-2K5cNRBR.js.map → content-query-I4DltOFE.js.map} +1 -1
  4. package/dist/chunks/{content-versions-D7GZKniP.js → content-versions-DIEWWzDF.js} +2 -2
  5. package/dist/chunks/{content-versions-D7GZKniP.js.map → content-versions-DIEWWzDF.js.map} +1 -1
  6. package/dist/chunks/{contents-BHGed_Rn.js → contents-a9VuKz61.js} +2 -2
  7. package/dist/chunks/{contents-BHGed_Rn.js.map → contents-a9VuKz61.js.map} +1 -1
  8. package/dist/content-query.d.ts.map +1 -1
  9. package/dist/index.js +4 -4
  10. package/dist/manifest.json +1 -1
  11. package/dist/server.js +1 -1
  12. package/dist/smrt-knowledge.json +5 -5
  13. package/dist/svelte/components/ContentAgentChat.svelte +2 -2
  14. package/dist/svelte/components/ContentBodyEditor.svelte +9 -9
  15. package/dist/svelte/components/ContentClaimAuditTool.svelte +1 -1
  16. package/dist/svelte/components/ContentContributionInbox.svelte +1 -1
  17. package/dist/svelte/components/ContentContributionPortal.svelte +1 -1
  18. package/dist/svelte/components/ContentContributionTypeManager.svelte +1 -1
  19. package/dist/svelte/components/ContentContributorManager.svelte +1 -1
  20. package/dist/svelte/components/ContentCorrectionsTool.svelte +1 -1
  21. package/dist/svelte/components/ContentEditor.svelte +17 -17
  22. package/dist/svelte/components/ContentGovernanceManager.svelte +1 -1
  23. package/dist/svelte/components/ContentGovernancePanel.svelte +6 -6
  24. package/dist/svelte/components/ContentImageBrowser.svelte +1 -1
  25. package/dist/svelte/components/ContentImageChooser.svelte +1 -1
  26. package/dist/svelte/components/ContentList.svelte +21 -21
  27. package/dist/svelte/components/ContentReferencesPanel.svelte +1 -1
  28. package/dist/svelte/components/ContentReviewStatusTray.svelte +1 -1
  29. package/dist/svelte/components/ContentTransparencyReport.svelte +2 -2
  30. package/dist/svelte/components/ContentTransparencyTool.svelte +1 -1
  31. package/dist/svelte/components/ContentVersionsTool.svelte +1 -1
  32. package/dist/svelte/components/GovernedContentEditor.svelte +1 -1
  33. package/dist/svelte/routes/ContentContributionsRoute.svelte +4 -4
  34. package/dist/svelte/routes/ContentFactsRoute.svelte +7 -7
  35. package/dist/svelte/routes/ContentGovernanceRoute.svelte +2 -2
  36. package/dist/svelte/routes/ContentWorkspaceRoute.svelte +2 -2
  37. package/dist/svelte/routes/PublishedArticleRoute.svelte +2 -2
  38. package/package.json +16 -16
@@ -3,14 +3,14 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-content",
6
- "packageVersion": "0.55.21",
6
+ "packageVersion": "0.55.23",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "de260cf5e06e94db202de64adb29016e44f9ea5fba4a1d16b699d40435113cc6",
11
- "packageJson": "4c15c857b05c2a970d643c18a3e7dd1f8c9dc10bb5c31bda15c958d13c91d830",
10
+ "manifest": "4ba823ef01e7ca8d668ec4d19ac9b26c2e4acf16154b7223cb6a80649b4c16ee",
11
+ "packageJson": "d510f0bee5fdedfbdc410c9eabd2e7346863c49799c66a86fffc0c9beafaa5da",
12
12
  "agents": "dffe54f5b13fa274d24056c6dc48179bd3bb4ef93c4e47670651f27b67523826",
13
- "moduleDoc:agents/content-list.md": "e5e990d6688f2a89fe521741e9af6e202dad5fa827307c727cfb5db92a83145a"
13
+ "moduleDoc:agents/content-list.md": "f616b006826d48af3e16e4f3e0693a7b656e9228b7aa987a791c31660a58da0f"
14
14
  },
15
15
  "exports": [
16
16
  ".",
@@ -14235,7 +14235,7 @@
14235
14235
  {
14236
14236
  "path": "agents/content-list.md",
14237
14237
  "module": "content-list",
14238
- "content": "<!-- Module doc for packages/content/AGENTS.md. -->\n\n# ContentList contracts\n\nRead for changes to `src/svelte/content-list-*.ts`, `ContentList.svelte`, or the\nserver content-query/action adapters. Browser exports come from `/svelte`;\nprincipal-bound query/action factories come from `/server`.\n\n## Ownership and integration\n\n- `content-list-controller.ts` is the shared adapter over `DataTableController`.\n Search, filters, sort, paging, selection, columns, and action eligibility have\n one implementation across grid, detailed, and compact presentations. Controller\n modes are manual. Local arrays use `toContentListRows`, `selectContentListRows`,\n then `paginateContentListRows`; server rows must never be transformed again.\n- `query`, `urlState`, `savedViews`, `lifecycle`, `jobs`, and `dataSurface` are\n optional bindings. Without query, rows come from `contents`; with it, contents\n is ignored. `type` locks/hides the type filter; `defaultViewMode` seeds once.\n Existing callbacks (`onEdit`, `onDelete`, `onAdd`, `getViewHref`) remain usable.\n- `query.bind()` runs once during component initialization so a structural\n `remoteQuery(collection, transport)` binding registers teardown in that scope.\n Do not import smrt-svelte/web or its browser data runtime into the content\n Svelte barrel: public content pages depend on that code-split boundary.\n- The default query transport targets `POST /api/v1/contents/query`.\n `CONTENT_LIST_QUERY_FIELDS` maps column IDs to server fields;\n `ROW_FIELD_TO_CONTENT_KEY` maps response fields to ContentData. For example,\n column `updated`, client `updatedAt`, and server `updated_at` differ.\n `publish` maps to `publish_date`. Derived `site` comes from url/source and has\n no server field; server filtering/sorting on it is dropped and reported.\n- Missing/duplicate durable IDs still render with positional keys but are\n `identified: false`. Disable and explain their selection controls. Page select\n all and normalization exclude synthetic IDs; server selection must survive\n page changes, so current-page membership is not a durability test.\n- Compact uses content-owned `select` and `actions` columns, included in\n `CONTENT_LIST_TABLE_COLUMN_IDS`. DataTable's generic selection cannot enforce\n per-row eligibility. Resolve links/actions through the shared adapter.\n\n## Paging and freshness\n\nContentList owns the pager in every presentation and passes no `totalRows` to\nDataTable, whose independent clamp would mutate the shared controller without\nknowing which query produced a count.\n\n- Clamp on the local full-array count or an exact total for the active settled\n server query only. Never clamp on an estimate, unavailable total, page length,\n previous-query response, or before the current response arrives. An estimate\n may still drive the displayed pager. Test paging in grid and compact modes.\n- Resolve one page-size ceiling with `resolveContentListMaxPageSize`: the minimum\n of configured limits and schema limit (default 200). Use it for controller\n seed, URL/saved-view sanitizers, translator, and live state. Server seed defaults\n to 50; null page size becomes the seed and oversized values become the ceiling,\n with reported drops. Local mode may remain unpaginated. Omitted URL size must\n restore the server seed. `setPageSize` resets page.\n- Offset only; maximum offset is 1,000,000. Report capping and update the marker\n to `effectivePage`; retain the notice until the operator leaves that page.\n Pager maximum is `floor(MAX_OFFSET / pageSize) + 1`, while clamp uses the true\n total so crafted out-of-range pages receive the explanatory notice.\n- Subscribe after the first request; query changes rebind the exact subscription.\n Offline retains usable stale rows. Online calls live `reconnect()` (refresh\n before resubscription), otherwise `refresh()`. Refresh errors are inline over\n retained rows. Show loading, retry, refreshing, last update, offline, truncation,\n and warnings across presentations. Read completeness flags from retry envelopes\n as well as execute envelopes; a successful retry replaces old flags.\n\n## Query authority and protocol\n\n`executeContentQuery()` applies tenancy, failing closed to global rows. Host\napplication scope comes from authenticated context, never request body; caller\nfilters only narrow it. Omitted scope means no extra application restriction;\n`scope: []` means no permitted rows (`id IS NULL`). AND scope into every query\nbranch alongside tenancy. Empty object conditions are configuration errors.\n\nKeep field maps, projectable fields, operator/search vocabulary, and mirrored\nlimits asserted against `buildContentQuerySchema()` and core constants or their\nobservable acceptance boundaries.\n\n- No cursor paging, body queries, metadata-path filters, or ETag/version slot.\n Read body through GET by ID; use queryFingerprint/freshness.asOf for freshness.\n- Search emits an `any` of `like` over title/description/author, escaping caller\n `%`/`_`. Drop and report unsupported `notContains`, datetime LIKE operators,\n invalid datetime inputs, unknown projection fields, and derived site queries.\n- Bound normalized request bytes, including default sort. Always emit sort\n (fall back to `CONTENT_LIST_QUERY_DEFAULT_SORT`) so client accounting is not\n smaller than the normalizer's. Limits: 100 list values, 50 filter nodes\n (containers count; search costs four), 128 DNF branches, 50 projection fields\n with 1–256-character IDs, 128-character request ID, 4096 UTF-16 units per scalar,\n 100,000 serialized request bytes. Count escapes and surrogate pairs without\n splitting code points. DNF costs include null unions and De Morgan negation.\n- Preserve comparands exactly or omit/report the filter. Oversized/unusable `in`\n entries may be removed as a reported subset (`out-of-range`/`unsupported-value`).\n Never partially apply `notIn`: drop/report `filter-widened`. Shed conjuncts for\n node/branch/byte budgets with that same report. Shortened LIKE/search patterns\n report widening; retain leading code points for contains/startsWith and trailing\n ones for endsWith. Never shorten scalar eq/ne/ordered comparands into a value\n the caller did not name. Every change is either exact, a reported superset,\n a reported subset, or a reported omitted predicate.\n- Datetimes require a real calendar day, four-digit year, T separator and an\n offset when time is present. Date-only means midnight UTC; minutes without\n seconds and lowercase t/z are accepted. Trim and uppercase before parsing,\n validate that same string, and calendar-round-trip to reject rolled dates.\n\n### Comparison semantics and dialect limits\n\nFold token filter values (type/status/state); preserve free-text case. Local\ntext comparison is case-insensitive; server free-text matching follows its SQL\noperator/dialect. PostgreSQL/DuckDB LIKE is case-sensitive; SQLite LIKE is ASCII\ncase-insensitive. Do not promise uniform free-text matching.\n\nSQLite's LIKE lacks the default backslash escape used by PostgreSQL/DuckDB;\nescaped wildcards can fail closed there. Fixing portability needs SQL-boundary\nESCAPE support. Null placement is likewise dialect-dependent: local sorts absent\nlast ascending/first descending, matching PostgreSQL/DuckDB; SQLite defaults\noppositely. The query sort grammar cannot express NULLS FIRST/LAST.\n\n- Read absence from original ContentData, not flattened display text. Type's\n `content` and title's `Untitled content` fallbacks are presentation only; missing\n values compare/search as null, genuinely blank strings remain present.\n- eq/in/LIKE exclude absent values unless null is explicitly listed. ne of a\n non-null value and notIn without null include absence. notIn containing null\n excludes absence. eq/ne null lower to IS NULL/IS NOT NULL. Direct ordered\n comparisons exclude absence; their negations include it. Every supported\n predicate and its negation must partition rows without gaps or overlap,\n including nested all/any and double negation. Negated LIKE is refused.\n- Preserve literal null through sanitization, saved views, translation and local\n evaluation; only undefined denotes unusable input. Test null, false, zero and\n empty strings. Blank scalar clears a filter; an empty entry within a list is\n real text, but a wholly empty list parameter is refused/reported.\n\n## Result budgets\n\nCall `assertContentQuerySchema(schema)` at configuration time. Every executor\nmode, including count, rejects maxResultBytes below\n`CONTENT_QUERY_MIN_RESULT_BYTES`; this is a host error, not caller validation.\n\n- Never drop rows to fit bytes: offset advances by requested limit, so omitted\n rows would become unreachable. Seat every row's irreducible floor first, then\n distribute surplus max-min fairly by appetite (cost minus floor).\n `allocateRowBytes` guarantees a page whenever floors fit; otherwise fail loudly.\n Preserve `floor <= cost` and refund the final unused array separator at exact\n byte boundaries.\n- Within rows use one sorted water-filling walk. Strings may shorten; datetime\n and JSON values are all-or-nothing (null only if smaller). Numbers, booleans,\n null, and identity never shrink. Prefer shortening free text before losing a\n format-constrained value; new constrained types are all-or-nothing by default.\n Report shortening with truncated/warnings.\n- Bound JSON documents before result normalization: strings 65,536 characters,\n containers 1,000 items, depth 16; finite numbers, no cycles, plain values only.\n Drop forbidden __proto__/constructor/prototype keys and build with null\n prototypes. Report truncation rather than allowing one stored document to\n invalidate an entire page.\n- Facets share the result byte budget; drop excess facet values and flag both\n facet and result. Per-value caps alone do not bound the envelope.\n\n## URL state and saved views\n\nRead urlState.params once; later onChange(params, state) preserves foreign\nparameters and lets the host own routing. `applyContentListViewState` is the\npublic application boundary: sanitize every patch, including raw saved snapshots,\nthen merge over current state without resetting restored page. Sanitize the patch,\nnot the merged result, because selection is intentionally absent from sanitizer\noutput. Keep raw saved-view payloads so stale-field drops remain reportable.\n\n- Escape list separators and double literal backslashes. List null uses the\n collision-free `\\0` token; a literal backslash-zero string is doubled. Scalar\n null serializes through isNull/isNotNull. Preserve foreign parameters by base\n name, even when they have a known operator suffix; unknown suffixed columns\n may still be reported as refused without being deleted.\n- The type lock applies to live state, including agent reset/filter commands.\n Fold it into restored patches to preserve paging. Clear it only on an actual\n lock-removal transition, not every unlocked render.\n- Toolbar selects must state the live predicate honestly. Unlisted equals values\n render as extra options and produce a notice. Lists, non-equals operators, and\n multiple predicates render a disabled summary and notice. Use operator-aware\n `readContentListSelectFilter`, not value-only `readContentListFilter`.\n Choosing a real option replaces all filters on that column. Summary sentinel\n uses U+001F; NUL changes during HTML parsing and breaks hydration.\n- Review is an ordinary status; deleted belongs exclusively to opt-in trash\n mode. Type options are display vocabulary, not the freeform model domain.\n Restore/translation refusals appear in one dismissible notice; stale state\n should still open with the refused parts omitted.\n\n## Shared mutations and lifecycle\n\nHuman transports and agent/bulk callers terminate at the same\n`createContentListActionAdapter()` instance. Preview/apply is principal-bound;\nrendered rows are never mutation authority.\n\n- Supported workflows: trash, draft, review submission, publish, archive, restore,\n automated review, formatting, categorization, optimization. Applications inject\n formatting/optimization handlers; handlers mutate supplied objects but must\n not save them because the adapter owns guarded writes.\n- Explicit/current-page/all-matching selections share a default 200-row cap.\n Match server and browser maxSelectionSize. All-matching is canonical query,\n fingerprint and exact count, never browser-page expansion. Above-cap requests\n fail closed, including forged/agent requests; UI asks the operator to narrow.\n- Read-only preview returns resolved scope/count/labels/eligibility/consequences.\n Apply uses opaque token and idempotency key, re-resolves query under live\n principal/tenant, rechecks tool/operation permissions and eligibility, and\n compares aggregate updated_at revision. Query/count/membership/row drift fails\n before mutation. Each save also guards updated_at atomically at UPDATE.\n Required projection: id, title, status, updated_at, even under host overrides.\n- Publication workflows require every snapshot read permission, explicitly\n including contentassets:read and assets:read. Keep assertions aligned with\n `ContentVersion.createSnapshot()`; embedded adapters rely on them without RLS.\n- Declare workflowStorage capability. PostgreSQL, SQLite, ordinary DuckDB and\n non-immediate exported-file modes are supported. JSON default/immediate and\n DuckDB immediate writes are rejected at setup because exports cannot roll back.\n- Partial results separate accepted/skipped/failed rows; clear only accepted IDs.\n Failures retain selection. Refresh the exact query and expose progress,\n exceptions, representative labels, resourceType/resourceId and auditReference.\n\nLifecycle binding enables trash controls; trash mode locks deleted and hides\nordinary status/edit/delete controls. Without it legacy onDelete remains.\n`createContentListLifecycleRoute()` mounts POST /api/v1/contents/lifecycle;\nits host authenticates every native Request and supplies current action context.\nOnly move-to-trash, restore, permanent-delete are accepted; malformed/auth/internal\nfailures use bounded envelopes without private error text.\n\n| Action | Eligible state | Contract |\n|---|---|---|\n| move-to-trash | not deleted | soft deletion |\n| restore | deleted | draft/review/published; recheck permissions and publication readiness |\n| permanent-delete | deleted | type exact server count; irreversible |\n\nPermanent DELETE binds updated_at in PostgreSQL's predicate with transactional\ncascade; embedded adapters compare under the shared write queue before cascade.\nRaces return row_revision_drifted. Empty-trash requires exact server count plus\ncanonical query. Query/selection changes invalidate previews; expiry, stale\nrevision, row/query/count drift or token failures require new preview. Preview,\ninitial apply and replay each get distinct attempt request IDs; apply/replay keep\nconfirmation/idempotency authority, and auditReference identifies original execution.\n\n## Jobs and agent surfaces\n\n- Automated review/formatting/optimization return job IDs. Queue callbacks repeat\n guarded apply with idempotency. Hosts must provide resolveDeferredPrincipal to\n re-resolve the complete live persona/TenantAgent binding; absent, mismatched or\n incomplete bindings fail closed. Authenticated jobStatusPath or client.status\n keeps identical intents locked while running, reconciles terminal outcomes,\n and allows failed/cancelled retry. Disable selection during status requests and\n recheck captured intent before reconciling responses.\n- `createContentListJobController().submit({ actionId, submissionKey, target },\n start)` coalesces duplicate active keys; rejected submissions stay failed until\n retry. update(job) cannot regress terminal state. Pending row work disables\n selection/edit/delete. Only succeeded transitions refresh: row targets require\n an affected visible row; query targets require the active request key. Failed\n jobs show error/retry and never masquerade as success.\n- Register dataSurface once at ContentList boundary across presentations and\n loading/empty/error states. Publish rendered columns plus required id (hidden\n description stays search-only), explicit fieldName mapping, available operators,\n query fingerprint/total, normalized selection, presentation and freshness.\n Derived site becomes read-only in server mode. Expose only mounted executable\n workflow/lifecycle actions. Context changes advance the same revision stream;\n stale revisions and disconnected registry commands are refused. set-view\n acknowledgements describe accepted state before renderer changes.\n- Silent reads use server `createContentListDataSurfaceDefinition({ collection,\n scope })` in createDataSurfaceTools. Resolve collection/scope from live principal;\n executeContentQuery owns rows/counts/facets/offset continuation. Exclude tenant,\n hidden and sensitive fields from discovery/projection. Durable agent mutations\n use the action adapter, never browser registry authority.\n\n## Validation\n\nUse package `pnpm test` (which generates routes through SvelteKit sync), never\nbare Vitest. Narrow with `-- <test-file>` to the affected content-list, query,\naction, lifecycle or component tests; include both grid and compact paging\nscenarios for reachability changes. Run package `pnpm typecheck` for Svelte\nchanges and root knowledge checks for this module documentation.\n"
14238
+ "content": "<!-- Module doc for packages/content/AGENTS.md. -->\n\n# ContentList contracts\n\nRead for changes to `src/svelte/content-list-*.ts`, `ContentList.svelte`, or the\nserver content-query/action adapters. Browser exports come from `/svelte`;\nprincipal-bound query/action factories come from `/server`.\n\n## Ownership and integration\n\n- `content-list-controller.ts` is the shared adapter over `DataTableController`.\n Search, filters, sort, paging, selection, columns, and action eligibility have\n one implementation across grid, detailed, and compact presentations. Controller\n modes are manual. Local arrays use `toContentListRows`, `selectContentListRows`,\n then `paginateContentListRows`; server rows must never be transformed again.\n- `query`, `urlState`, `savedViews`, `lifecycle`, `jobs`, and `dataSurface` are\n optional bindings. Without query, rows come from `contents`; with it, contents\n is ignored. `type` locks/hides the type filter; `defaultViewMode` seeds once.\n Existing callbacks (`onEdit`, `onDelete`, `onAdd`, `getViewHref`) remain usable.\n- `query.bind()` runs once during component initialization so a structural\n `remoteQuery(collection, transport)` binding registers teardown in that scope.\n Do not import smrt-svelte/web or its browser data runtime into the content\n Svelte barrel: public content pages depend on that code-split boundary.\n- The default query transport targets `POST /api/v1/contents/query`.\n `CONTENT_LIST_QUERY_FIELDS` maps column IDs to server fields;\n `ROW_FIELD_TO_CONTENT_KEY` maps response fields to ContentData. For example,\n column `updated`, client `updatedAt`, and server `updated_at` differ.\n `publish` maps to `publish_date`. Derived `site` comes from url/source and has\n no server field; server filtering/sorting on it is dropped and reported.\n- Missing/duplicate durable IDs still render with positional keys but are\n `identified: false`. Disable and explain their selection controls. Page select\n all and normalization exclude synthetic IDs; server selection must survive\n page changes, so current-page membership is not a durability test.\n- Compact uses content-owned `select` and `actions` columns, included in\n `CONTENT_LIST_TABLE_COLUMN_IDS`. DataTable's generic selection cannot enforce\n per-row eligibility. Resolve links/actions through the shared adapter.\n\n## Paging and freshness\n\nContentList owns the pager in every presentation and passes no `totalRows` to\nDataTable, whose independent clamp would mutate the shared controller without\nknowing which query produced a count.\n\n- Clamp on the local full-array count or an exact total for the active settled\n server query only. Never clamp on an estimate, unavailable total, page length,\n previous-query response, or before the current response arrives. An estimate\n may still drive the displayed pager. Test paging in grid and compact modes.\n- Resolve one page-size ceiling with `resolveContentListMaxPageSize`: the minimum\n of configured limits and schema limit (default 200). Use it for controller\n seed, URL/saved-view sanitizers, translator, and live state. Server seed defaults\n to 50; null page size becomes the seed and oversized values become the ceiling,\n with reported drops. Local mode may remain unpaginated. Omitted URL size must\n restore the server seed. `setPageSize` resets page.\n- Offset only; maximum offset is 1,000,000. Report capping and update the marker\n to `effectivePage`; retain the notice until the operator leaves that page.\n Pager maximum is `floor(MAX_OFFSET / pageSize) + 1`, while clamp uses the true\n total so crafted out-of-range pages receive the explanatory notice.\n- Subscribe after the first request; query changes rebind the exact subscription.\n Offline retains usable stale rows. Online calls live `reconnect()` (refresh\n before resubscription), otherwise `refresh()`. Refresh errors are inline over\n retained rows. Show loading, retry, refreshing, last update, offline, truncation,\n and warnings across presentations. Read completeness flags from retry envelopes\n as well as execute envelopes; a successful retry replaces old flags.\n\n## Query authority and protocol\n\n`executeContentQuery()` applies tenancy, failing closed to global rows. Host\napplication scope comes from authenticated context, never request body; caller\nfilters only narrow it. Omitted scope means no extra application restriction;\n`scope: []` means no permitted rows (`id IS NULL`). AND scope into every query\nbranch alongside tenancy. Empty object conditions are configuration errors.\n\nKeep field maps, projectable fields, operator/search vocabulary, and mirrored\nlimits asserted against `buildContentQuerySchema()` and core constants or their\nobservable acceptance boundaries.\n\n- No cursor paging, body queries, metadata-path filters, or ETag/version slot.\n Read body through GET by ID; use queryFingerprint/freshness.asOf for freshness.\n- Search emits an `any` of `like` over title/description/author, escaping caller\n `%`/`_`. Drop and report unsupported `notContains`, datetime LIKE operators,\n invalid datetime inputs, unknown projection fields, and derived site queries.\n- Bound normalized request bytes, including default sort. Always emit sort\n (fall back to `CONTENT_LIST_QUERY_DEFAULT_SORT`) so client accounting is not\n smaller than the normalizer's. Limits: 100 list values, 50 filter nodes\n (containers count; search costs four), 128 DNF branches, 50 projection fields\n with 1–256-character IDs, 128-character request ID, 4096 UTF-16 units per scalar,\n 100,000 serialized request bytes. Count escapes and surrogate pairs without\n splitting code points. DNF costs include null unions and De Morgan negation.\n- Preserve comparands exactly or omit/report the filter. Oversized/unusable `in`\n entries may be removed as a reported subset (`out-of-range`/`unsupported-value`).\n Never partially apply `notIn`: drop/report `filter-widened`. Shed conjuncts for\n node/branch/byte budgets with that same report. Shortened LIKE/search patterns\n report widening; retain leading code points for contains/startsWith and trailing\n ones for endsWith. Never shorten scalar eq/ne/ordered comparands into a value\n the caller did not name. Every change is either exact, a reported superset,\n a reported subset, or a reported omitted predicate.\n- Datetimes require a real calendar day, four-digit year, T separator and an\n offset when time is present. Date-only means midnight UTC; minutes without\n seconds and lowercase t/z are accepted. Trim and uppercase before parsing,\n validate that same string, and calendar-round-trip to reject rolled dates.\n\n### Comparison semantics and dialect limits\n\nFold token filter values (type/status/state); preserve free-text case. Local\ntext comparison is case-insensitive; server free-text matching follows its SQL\noperator/dialect. PostgreSQL/DuckDB LIKE is case-sensitive; SQLite LIKE is ASCII\ncase-insensitive. Do not promise uniform free-text matching.\n\nSQLite's LIKE lacks the default backslash escape used by PostgreSQL/DuckDB;\nescaped wildcards can fail closed there. Fixing portability needs SQL-boundary\nESCAPE support. Null placement is likewise dialect-dependent: local sorts absent\nlast ascending/first descending, matching PostgreSQL/DuckDB; SQLite defaults\noppositely. The query sort grammar cannot express NULLS FIRST/LAST.\n\n- Read absence from original ContentData, not flattened display text. Type's\n `content` and title's `Untitled content` fallbacks are presentation only; missing\n values compare/search as null, genuinely blank strings remain present.\n- eq/in/LIKE exclude absent values unless null is explicitly listed. ne of a\n non-null value and notIn without null include absence. notIn containing null\n excludes absence. eq/ne null lower to IS NULL/IS NOT NULL. Direct ordered\n comparisons exclude absence; their negations include it. Every supported\n predicate and its negation must partition rows without gaps or overlap,\n including nested all/any and double negation. Negated LIKE is refused.\n- Preserve literal null through sanitization, saved views, translation and local\n evaluation; only undefined denotes unusable input. Test null, false, zero and\n empty strings. Blank scalar clears a filter; an empty entry within a list is\n real text, but a wholly empty list parameter is refused/reported.\n\n## Result budgets\n\nCall `assertContentQuerySchema(schema)` at configuration time. Every executor\nmode, including count, rejects maxResultBytes below\n`CONTENT_QUERY_MIN_RESULT_BYTES`; this is a host error, not caller validation.\n\n- Never drop rows to fit bytes: offset advances by requested limit, so omitted\n rows would become unreachable. Seat every row's irreducible floor first, then\n distribute surplus max-min fairly by appetite (cost minus floor).\n `allocateRowBytes` guarantees a page whenever floors fit; otherwise fail loudly.\n Preserve `floor <= cost` and refund the final unused array separator at exact\n byte boundaries.\n- Within rows use one sorted water-filling walk. Strings may shorten; datetime\n and JSON values are all-or-nothing (null only if smaller). Numbers, booleans,\n null, and identity never shrink. Prefer shortening free text before losing a\n format-constrained value; new constrained types are all-or-nothing by default.\n Report shortening with truncated/warnings.\n- Bound JSON documents before result normalization: strings 65,536 characters,\n containers 1,000 items, depth 16; finite numbers, no cycles, plain values only.\n Drop forbidden __proto__/constructor/prototype keys and build with null\n prototypes. Report truncation rather than allowing one stored document to\n invalidate an entire page.\n- Facets share the result byte budget; drop excess facet values and flag both\n facet and result. Per-value caps alone do not bound the envelope.\n\n## URL state and saved views\n\nRead urlState.params once; later onChange(params, state) preserves foreign\nparameters and lets the host own routing. `applyContentListViewState` is the\npublic application boundary: sanitize every patch, including raw saved snapshots,\nthen merge over current state without resetting restored page. Sanitize the patch,\nnot the merged result, because selection is intentionally absent from sanitizer\noutput. Keep raw saved-view payloads so stale-field drops remain reportable.\n\n- Escape list separators and double literal backslashes. List null uses the\n collision-free `\\0` token; a literal backslash-zero string is doubled. Scalar\n null serializes through isNull/isNotNull. Preserve foreign parameters by base\n name, even when they have a known operator suffix; unknown suffixed columns\n may still be reported as refused without being deleted.\n- The type lock applies to live state, including agent reset/filter commands.\n Fold it into restored patches to preserve paging. Clear it only on an actual\n lock-removal transition, not every unlocked render.\n- Toolbar selects must state the live predicate honestly. Unlisted equals values\n render as extra options and produce a notice. Lists, non-equals operators, and\n multiple predicates render a disabled summary and notice. Use operator-aware\n `readContentListSelectFilter`, not value-only `readContentListFilter`.\n Choosing a real option replaces all filters on that column. Summary sentinel\n uses U+001F; NUL changes during HTML parsing and breaks hydration.\n- Review is an ordinary status; deleted belongs exclusively to opt-in trash\n mode. Type options are display vocabulary, not the freeform model domain.\n Restore/translation refusals appear in one dismissible notice; stale state\n should still open with the refused parts omitted.\n\n## Shared mutations and lifecycle\n\nHuman transports and agent/bulk callers terminate at the same\n`createContentListActionAdapter()` instance. Preview/apply is principal-bound;\nrendered rows are never mutation authority.\n\n- Supported workflows: trash, draft, review submission, publish, archive, restore,\n automated review, formatting, categorization, optimization. Applications inject\n formatting/optimization handlers; handlers mutate supplied objects but must\n not save them because the adapter owns guarded writes.\n- Explicit/current-page/all-matching selections share a default 200-row cap.\n Match server and browser maxSelectionSize. All-matching is canonical query,\n fingerprint and exact count, never browser-page expansion. Above-cap requests\n fail closed, including forged/agent requests; UI asks the operator to narrow.\n- Read-only preview returns resolved scope/count/labels/eligibility/consequences.\n Apply uses opaque token and idempotency key, re-resolves query under live\n principal/tenant, rechecks tool/operation permissions and eligibility, and\n compares aggregate updated_at revision. Query/count/membership/row drift fails\n before mutation. Each save also guards updated_at atomically at UPDATE.\n Required projection: id, title, status, updated_at, even under host overrides.\n- Publication workflows require every snapshot read permission, explicitly\n including contentassets:read and assets:read. Keep assertions aligned with\n `ContentVersion.createSnapshot()`; embedded adapters rely on them without RLS.\n- Declare workflowStorage capability. PostgreSQL, SQLite, ordinary DuckDB and\n non-immediate exported-file modes are supported. JSON default/immediate and\n DuckDB immediate writes are rejected at setup because exports cannot roll back.\n- Partial results separate accepted/skipped/failed rows; clear only accepted IDs.\n Failures retain selection. Refresh the exact query and expose progress,\n exceptions, representative labels, resourceType/resourceId and auditReference.\n\nLifecycle binding enables trash controls; trash mode locks deleted and hides\nordinary status/edit/delete controls. Without it legacy onDelete remains.\n`createContentListLifecycleRoute()` mounts POST /api/v1/contents/lifecycle;\nits host authenticates every native Request and supplies current action context.\nOnly move-to-trash, restore, permanent-delete are accepted; malformed/auth/internal\nfailures use bounded envelopes without private error text.\n\n| Action | Eligible state | Contract |\n|---|---|---|\n| move-to-trash | not deleted | soft deletion |\n| restore | deleted | draft/review/published; recheck permissions and publication readiness |\n| permanent-delete | deleted | type exact server count; irreversible |\n\nPermanent DELETE binds updated_at in PostgreSQL's predicate with transactional\ncascade; embedded adapters compare under the shared write queue before cascade.\nRaces return row_revision_drifted. Empty-trash requires exact server count plus\ncanonical query. Query/selection changes invalidate previews; expiry, stale\nrevision, row/query/count drift or token failures require new preview. Preview,\ninitial apply and replay each get distinct attempt request IDs; apply/replay keep\nconfirmation/idempotency authority, and auditReference identifies original execution.\n\n## Jobs and agent surfaces\n\n- Automated review/formatting/optimization return job IDs. Queue callbacks repeat\n guarded apply with idempotency. Hosts must provide resolveDeferredPrincipal to\n re-resolve the complete live persona/TenantAgent binding; absent, mismatched or\n incomplete bindings fail closed. Authenticated jobStatusPath or client.status\n keeps identical intents locked while running, reconciles terminal outcomes,\n and allows failed/cancelled retry. Disable selection during status requests and\n recheck captured intent before reconciling responses.\n- `createContentListJobController().submit({ actionId, submissionKey, target },\n start)` coalesces duplicate active keys; rejected submissions stay failed until\n retry. update(job) cannot regress terminal state. Pending row work disables\n selection/edit/delete. Only succeeded transitions refresh: row targets require\n an affected visible row; query targets require the active request key. Failed\n jobs show error/retry and never masquerade as success.\n- Register dataSurface once at ContentList boundary across presentations and\n loading/empty/error states. Publish rendered columns plus required id (hidden\n description stays search-only), explicit fieldName mapping, available operators,\n query fingerprint/total, normalized selection, presentation and freshness.\n Derived site becomes read-only in server mode. Expose only mounted executable\n workflow/lifecycle actions. Context changes advance the same revision stream;\n stale revisions and disconnected registry commands are refused. set-view\n acknowledgements describe accepted state before renderer changes.\n- Silent reads use server `createContentListDataSurfaceDefinition({ collection,\n scope })` in createDataSurfaceTools. Resolve collection/scope from live principal;\n executeContentQuery owns rows/counts/facets/offset continuation. Exclude tenant,\n hidden and sensitive fields from discovery/projection. Durable agent mutations\n use the action adapter, never browser registry authority.\n\n## Validation\n\nUse package `pnpm test` (which generates routes through SvelteKit sync), never\nbare Vitest. Narrow with `-- <test-file>` to the affected content-list, query,\naction, lifecycle or component tests; include both grid and compact paging\nscenarios for reachability changes. Run package `pnpm typecheck` for Svelte\nchanges and root knowledge checks for this module documentation.\n\n### Hydrated result values\n\nThe query boundary normalizes values against their declared descriptors before\nbyte budgeting. Objects hydrated from text columns become JSON strings and use\nthe existing scalar shortening/warning path; JSON columns retain their document\nlimits. Date-only and timezone-less datetime strings mean UTC, including facet\nbuckets. Bigint values must remain safely representable as JavaScript numbers.\n"
14239
14239
  }
14240
14240
  ],
14241
14241
  "browser": {
@@ -629,7 +629,7 @@ async function handleSendMessage(content: string) {
629
629
  align-items: center;
630
630
  gap: var(--smrt-spacing-1);
631
631
  padding: var(--smrt-spacing-1) var(--smrt-spacing-2);
632
- border: 1px solid var(--smrt-color-outline-variant);
632
+ border: var(--smrt-hairline);
633
633
  border-radius: var(--smrt-radius-sm);
634
634
  background: none;
635
635
  color: var(--smrt-color-on-surface-variant);
@@ -660,7 +660,7 @@ async function handleSendMessage(content: string) {
660
660
  .new-topic-row :global(.new-topic-input) {
661
661
  flex: 1;
662
662
  padding: var(--smrt-spacing-1) var(--smrt-spacing-2);
663
- border: 1px solid var(--smrt-color-outline-variant);
663
+ border: var(--smrt-hairline);
664
664
  border-radius: var(--smrt-radius-sm);
665
665
  font-size: var(--smrt-typography-body-medium-size, 0.8125rem);
666
666
  outline: none;
@@ -1935,7 +1935,7 @@ function handleEditorDragEnd() {
1935
1935
  position: relative;
1936
1936
  display: flex;
1937
1937
  flex-direction: column;
1938
- border: 1px solid var(--smrt-color-outline-variant);
1938
+ border: var(--smrt-hairline);
1939
1939
  border-radius: 0.85rem;
1940
1940
  background: var(--smrt-color-surface);
1941
1941
  overflow: visible;
@@ -1946,7 +1946,7 @@ function handleEditorDragEnd() {
1946
1946
  align-items: center;
1947
1947
  gap: 0.35rem;
1948
1948
  padding: 0.45rem 0.55rem;
1949
- border-bottom: 1px solid var(--smrt-color-outline-variant);
1949
+ border-bottom: var(--smrt-hairline);
1950
1950
  background: var(--smrt-color-surface-container-low, var(--smrt-color-surface-container));
1951
1951
  }
1952
1952
 
@@ -1997,7 +1997,7 @@ function handleEditorDragEnd() {
1997
1997
  }
1998
1998
 
1999
1999
  .body-editor-toolbar :global(.editor-toolbar-button:hover) {
2000
- border-color: var(--smrt-color-outline-variant);
2000
+ border-color: var(--smrt-hairline-color);
2001
2001
  background: var(--smrt-color-surface-container);
2002
2002
  }
2003
2003
 
@@ -2009,7 +2009,7 @@ function handleEditorDragEnd() {
2009
2009
  the story rather than below it. Full width, in the page flow (no modal),
2010
2010
  so it works the same on a phone. */
2011
2011
  .body-editor-image-panel {
2012
- border-bottom: 1px solid var(--smrt-color-outline-variant);
2012
+ border-bottom: var(--smrt-hairline);
2013
2013
  background: var(--smrt-color-surface-container-lowest, var(--smrt-color-surface));
2014
2014
  padding: 0.75rem;
2015
2015
  max-height: min(70vh, 40rem);
@@ -2031,9 +2031,9 @@ function handleEditorDragEnd() {
2031
2031
  color: var(--smrt-color-on-surface-variant);
2032
2032
  }
2033
2033
 
2034
- .format-select :global(.select) {
2034
+ .format-select :global([data-smrt-component="Select"]) {
2035
2035
  min-height: 2rem;
2036
- border: 1px solid var(--smrt-color-outline-variant);
2036
+ border: var(--smrt-hairline);
2037
2037
  border-radius: 0.45rem;
2038
2038
  background: var(--smrt-color-surface);
2039
2039
  color: var(--smrt-color-on-surface);
@@ -2211,7 +2211,7 @@ function handleEditorDragEnd() {
2211
2211
  inline-size: 44px;
2212
2212
  block-size: 44px;
2213
2213
  padding: 0;
2214
- border: 1px solid var(--smrt-color-outline-variant);
2214
+ border: var(--smrt-hairline);
2215
2215
  border-radius: var(--smrt-radius-full);
2216
2216
  background: color-mix(in srgb, var(--smrt-color-surface) 94%, transparent);
2217
2217
  color: var(--smrt-color-primary);
@@ -2255,7 +2255,7 @@ function handleEditorDragEnd() {
2255
2255
  align-items: center;
2256
2256
  gap: 0.1rem;
2257
2257
  padding: 0.25rem;
2258
- border: 1px solid var(--smrt-color-outline-variant);
2258
+ border: var(--smrt-hairline);
2259
2259
  border-radius: var(--smrt-radius-full);
2260
2260
  background: color-mix(in srgb, var(--smrt-color-surface) 96%, transparent);
2261
2261
  color: var(--smrt-color-on-surface);
@@ -2324,7 +2324,7 @@ function handleEditorDragEnd() {
2324
2324
  z-index: 21;
2325
2325
  width: 1.45rem;
2326
2326
  height: 1.45rem;
2327
- border: 1px solid var(--smrt-color-outline-variant);
2327
+ border: var(--smrt-hairline);
2328
2328
  border-radius: var(--smrt-radius-full);
2329
2329
  background: var(--smrt-color-surface);
2330
2330
  box-shadow: var(--smrt-elevation-4, 0 0.45rem 1rem color-mix(in srgb, var(--smrt-color-shadow) 18%, transparent));
@@ -358,7 +358,7 @@ async function recheckFactClaims(claimIds: string[]) {
358
358
  }
359
359
 
360
360
  .tool-card {
361
- border: var(--smrt-card-border, 1px solid var(--smrt-color-outline-variant));
361
+ border: var(--smrt-card-border, var(--smrt-hairline));
362
362
  box-shadow: var(--smrt-card-shadow, none);
363
363
  border-radius: 0.65rem;
364
364
  background: var(--smrt-card-background, var(--smrt-color-surface));
@@ -281,7 +281,7 @@ $effect(() => {
281
281
 
282
282
  .inbox__list button,
283
283
  .inbox__detail {
284
- border: 1px solid var(--smrt-color-outline-variant);
284
+ border: var(--smrt-hairline);
285
285
  border-radius: 0.75rem;
286
286
  padding: 0.85rem;
287
287
  background: var(--smrt-color-surface);
@@ -133,7 +133,7 @@ function canWithdraw(contribution: ContentContributionData) {
133
133
 
134
134
  .portal__list button,
135
135
  .portal__detail {
136
- border: 1px solid var(--smrt-color-outline-variant);
136
+ border: var(--smrt-hairline);
137
137
  border-radius: 0.75rem;
138
138
  padding: 0.85rem;
139
139
  background: var(--smrt-color-surface);
@@ -284,7 +284,7 @@ function handleSubmit() {
284
284
 
285
285
  .card,
286
286
  .editor-shell :global(.editor) {
287
- border: 1px solid var(--smrt-color-outline-variant);
287
+ border: var(--smrt-hairline);
288
288
  border-radius: 0.75rem;
289
289
  padding: 0.9rem;
290
290
  background: var(--smrt-color-surface);
@@ -121,7 +121,7 @@ function handleSubmit() {
121
121
 
122
122
  .card,
123
123
  .editor-shell :global(.editor) {
124
- border: 1px solid var(--smrt-color-outline-variant);
124
+ border: var(--smrt-hairline);
125
125
  border-radius: 0.75rem;
126
126
  padding: 0.9rem;
127
127
  background: var(--smrt-color-surface);
@@ -282,7 +282,7 @@ async function issueCorrection() {
282
282
  flex-direction: column;
283
283
  gap: 0.4rem;
284
284
  background: var(--smrt-card-background, var(--smrt-color-surface));
285
- border: var(--smrt-card-border, 1px solid var(--smrt-color-outline-variant));
285
+ border: var(--smrt-card-border, var(--smrt-hairline));
286
286
  box-shadow: var(--smrt-card-shadow, none);
287
287
  border-radius: 0.75rem;
288
288
  padding: 0.85rem;
@@ -1584,7 +1584,7 @@ function removeAsset(id: string) {
1584
1584
  justify-content: space-between;
1585
1585
  padding-bottom: 1.5rem;
1586
1586
  margin-bottom: 1.5rem;
1587
- border-bottom: 1px solid var(--smrt-color-outline-variant);
1587
+ border-bottom: var(--smrt-hairline);
1588
1588
  }
1589
1589
 
1590
1590
  .editor-toolbar-left {
@@ -1623,7 +1623,7 @@ function removeAsset(id: string) {
1623
1623
  .mui-field :global(.mui-input) {
1624
1624
  padding: 0.5rem 0.75rem;
1625
1625
  border-radius: 0.375rem;
1626
- border: 1px solid var(--smrt-color-outline-variant);
1626
+ border: var(--smrt-hairline);
1627
1627
  background: transparent;
1628
1628
  color: var(--smrt-color-on-surface);
1629
1629
  font-size: var(--smrt-typography-body-medium-size, 0.8125rem);
@@ -1703,7 +1703,7 @@ function removeAsset(id: string) {
1703
1703
  height: 600px;
1704
1704
  background: var(--smrt-color-surface);
1705
1705
  border-radius: 1rem;
1706
- border: 1px solid var(--smrt-color-outline-variant);
1706
+ border: var(--smrt-hairline);
1707
1707
  overflow: hidden;
1708
1708
  }
1709
1709
 
@@ -1743,7 +1743,7 @@ function removeAsset(id: string) {
1743
1743
  flex-direction: column;
1744
1744
  gap: 0.75rem;
1745
1745
  background: var(--smrt-color-surface-container-low);
1746
- border: 1px solid var(--smrt-color-outline-variant);
1746
+ border: var(--smrt-hairline);
1747
1747
  padding: 1rem;
1748
1748
  border-radius: 0.5rem;
1749
1749
  }
@@ -1758,7 +1758,7 @@ function removeAsset(id: string) {
1758
1758
  display: flex;
1759
1759
  align-items: center;
1760
1760
  background: var(--smrt-color-surface);
1761
- border: 1px solid var(--smrt-color-outline);
1761
+ border: var(--smrt-hairline-strong);
1762
1762
  border-radius: var(--smrt-radius-full);
1763
1763
  padding: 0.25rem 0.25rem 0.25rem 0.75rem;
1764
1764
  font-size: var(--smrt-typography-label-large-size, 0.875rem);
@@ -1794,7 +1794,7 @@ function removeAsset(id: string) {
1794
1794
  }
1795
1795
 
1796
1796
  .reference-detail {
1797
- border: 1px solid var(--smrt-color-outline-variant);
1797
+ border: var(--smrt-hairline);
1798
1798
  background: var(--smrt-color-surface);
1799
1799
  border-radius: 0.5rem;
1800
1800
  padding: 0.75rem;
@@ -1828,7 +1828,7 @@ function removeAsset(id: string) {
1828
1828
 
1829
1829
  .references-section :global(.evidence-action-button) {
1830
1830
  background: var(--smrt-color-surface);
1831
- border: 1px solid var(--smrt-color-outline-variant);
1831
+ border: var(--smrt-hairline);
1832
1832
  border-radius: 0.375rem;
1833
1833
  color: var(--smrt-color-on-surface);
1834
1834
  cursor: pointer;
@@ -1845,7 +1845,7 @@ function removeAsset(id: string) {
1845
1845
  .evidence-bulk-toolbar {
1846
1846
  align-items: center;
1847
1847
  background: var(--smrt-color-surface);
1848
- border: 1px solid var(--smrt-color-outline-variant);
1848
+ border: var(--smrt-hairline);
1849
1849
  border-radius: 0.5rem;
1850
1850
  display: flex;
1851
1851
  flex-wrap: wrap;
@@ -1859,8 +1859,8 @@ function removeAsset(id: string) {
1859
1859
  font-size: var(--smrt-typography-body-medium-size, 0.8125rem);
1860
1860
  }
1861
1861
 
1862
- .evidence-bulk-toolbar :global(.select) {
1863
- border: 1px solid var(--smrt-color-outline-variant);
1862
+ .evidence-bulk-toolbar :global([data-smrt-component="Select"]) {
1863
+ border: var(--smrt-hairline);
1864
1864
  border-radius: 0.375rem;
1865
1865
  padding: 0.25rem 0.5rem;
1866
1866
  }
@@ -1926,7 +1926,7 @@ function removeAsset(id: string) {
1926
1926
  }
1927
1927
 
1928
1928
  .resource-claim {
1929
- border-top: 1px solid var(--smrt-color-outline-variant);
1929
+ border-top: var(--smrt-hairline);
1930
1930
  padding-top: 0.65rem;
1931
1931
  }
1932
1932
 
@@ -1949,7 +1949,7 @@ function removeAsset(id: string) {
1949
1949
 
1950
1950
  .evidence-status {
1951
1951
  align-items: center;
1952
- border: 1px solid var(--smrt-color-outline-variant);
1952
+ border: var(--smrt-hairline);
1953
1953
  border-radius: var(--smrt-radius-full);
1954
1954
  display: inline-flex;
1955
1955
  font-size: var(--smrt-typography-label-medium-size, 0.75rem);
@@ -2037,13 +2037,13 @@ function removeAsset(id: string) {
2037
2037
  gap: 0.5rem;
2038
2038
  }
2039
2039
 
2040
- .add-reference-row :global(.input) {
2040
+ .add-reference-row :global([data-smrt-component="Input"]) {
2041
2041
  flex: 1;
2042
2042
  }
2043
2043
 
2044
2044
  .add-reference-row :global(.add-reference-button) {
2045
2045
  background: var(--smrt-color-surface);
2046
- border: 1px solid var(--smrt-color-outline);
2046
+ border: var(--smrt-hairline-strong);
2047
2047
  color: var(--smrt-color-on-surface-variant);
2048
2048
  padding: 0 1rem;
2049
2049
  border-radius: 0.5rem;
@@ -2060,7 +2060,7 @@ function removeAsset(id: string) {
2060
2060
  align-items: center;
2061
2061
  gap: 0.5rem;
2062
2062
  background: var(--smrt-color-surface, white);
2063
- border: 1px solid var(--smrt-color-outline);
2063
+ border: var(--smrt-hairline-strong);
2064
2064
  padding: 0.75rem 1.25rem;
2065
2065
  border-radius: var(--smrt-radius-md, 0.5rem);
2066
2066
  color: var(--smrt-color-on-surface-variant);
@@ -2112,7 +2112,7 @@ function removeAsset(id: string) {
2112
2112
  max-height: 60vh;
2113
2113
  overflow-y: auto;
2114
2114
  background: var(--smrt-color-surface);
2115
- border: 1px solid var(--smrt-color-outline-variant);
2115
+ border: var(--smrt-hairline);
2116
2116
  margin-top: 1rem;
2117
2117
  border-radius: 0.75rem;
2118
2118
  box-shadow: var(--smrt-elevation-2, 0 4px 6px -1px color-mix(in srgb, var(--smrt-color-shadow) 5%, transparent), 0 2px 4px -1px color-mix(in srgb, var(--smrt-color-shadow) 3%, transparent));
@@ -2160,7 +2160,7 @@ function removeAsset(id: string) {
2160
2160
  .undo-banner :global(.undo-banner__btn) {
2161
2161
  background: var(--smrt-color-surface);
2162
2162
  color: var(--smrt-color-primary);
2163
- border: 1px solid var(--smrt-color-outline);
2163
+ border: var(--smrt-hairline-strong);
2164
2164
  padding: 0.375rem 0.875rem;
2165
2165
  border-radius: 0.375rem;
2166
2166
  font-size: var(--smrt-typography-label-large-size, 0.8125rem);
@@ -328,7 +328,7 @@ function cancelEditing() {
328
328
  }
329
329
 
330
330
  .card {
331
- border: 1px solid var(--smrt-color-outline-variant);
331
+ border: var(--smrt-hairline);
332
332
  border-radius: 0.75rem;
333
333
  padding: 0.85rem;
334
334
  display: flex;
@@ -1916,7 +1916,7 @@ function getVersionProvenanceCopy(version: ContentVersionData) {
1916
1916
 
1917
1917
  .workflow-section {
1918
1918
  background: var(--smrt-color-surface-container-low);
1919
- border: 1px solid var(--smrt-color-outline-variant);
1919
+ border: var(--smrt-hairline);
1920
1920
  border-radius: 0.75rem;
1921
1921
  padding: 1rem;
1922
1922
  display: flex;
@@ -2012,7 +2012,7 @@ function getVersionProvenanceCopy(version: ContentVersionData) {
2012
2012
  }
2013
2013
 
2014
2014
  .claim-audit-item {
2015
- border: 1px solid var(--smrt-color-outline-variant);
2015
+ border: var(--smrt-hairline);
2016
2016
  border-radius: 0.6rem;
2017
2017
  background: var(--smrt-color-surface);
2018
2018
  padding: 0.75rem;
@@ -2049,7 +2049,7 @@ function getVersionProvenanceCopy(version: ContentVersionData) {
2049
2049
  .fact-result,
2050
2050
  .review-card {
2051
2051
  background: var(--smrt-color-surface);
2052
- border: 1px solid var(--smrt-color-outline-variant);
2052
+ border: var(--smrt-hairline);
2053
2053
  border-radius: 0.75rem;
2054
2054
  padding: 0.85rem;
2055
2055
  }
@@ -2096,7 +2096,7 @@ function getVersionProvenanceCopy(version: ContentVersionData) {
2096
2096
  flex-direction: column;
2097
2097
  gap: 0.75rem;
2098
2098
  background: var(--smrt-color-surface);
2099
- border: 1px solid var(--smrt-color-outline-variant);
2099
+ border: var(--smrt-hairline);
2100
2100
  border-radius: 0.75rem;
2101
2101
  padding: 0.85rem;
2102
2102
  }
@@ -2130,7 +2130,7 @@ function getVersionProvenanceCopy(version: ContentVersionData) {
2130
2130
 
2131
2131
  .review-profile-item {
2132
2132
  padding: 0.65rem 0.75rem;
2133
- border: 1px solid var(--smrt-color-outline-variant);
2133
+ border: var(--smrt-hairline);
2134
2134
  border-radius: 0.65rem;
2135
2135
  background: var(--smrt-color-surface-container-low);
2136
2136
  }
@@ -2180,7 +2180,7 @@ function getVersionProvenanceCopy(version: ContentVersionData) {
2180
2180
  .transparency-card,
2181
2181
  .transparency-list__item {
2182
2182
  background: var(--smrt-color-surface);
2183
- border: 1px solid var(--smrt-color-outline-variant);
2183
+ border: var(--smrt-hairline);
2184
2184
  border-radius: 0.75rem;
2185
2185
  }
2186
2186
 
@@ -216,7 +216,7 @@ function handleSelect(selected: ImageLike | File | string) {
216
216
  justify-content: center;
217
217
  gap: 0.45rem;
218
218
  min-height: 2.35rem;
219
- border: 1px solid var(--smrt-color-outline);
219
+ border: var(--smrt-hairline-strong);
220
220
  border-radius: 0.5rem;
221
221
  background: var(--smrt-color-surface-container);
222
222
  color: var(--smrt-color-on-surface);
@@ -108,7 +108,7 @@ function cycle(delta: number) {
108
108
 
109
109
  .content-image-chooser :global(.chooser-arrow),
110
110
  .content-image-chooser :global(.chooser-preview) {
111
- border: 1px solid var(--smrt-color-outline-variant);
111
+ border: var(--smrt-hairline);
112
112
  background: var(--smrt-color-surface-container);
113
113
  color: var(--smrt-color-on-surface);
114
114
  border-radius: var(--smrt-radius-full);
@@ -3457,10 +3457,10 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
3457
3457
  align-items: center;
3458
3458
  }
3459
3459
 
3460
- .search-filters :global(.input),
3461
- .search-filters :global(.select) {
3460
+ .search-filters :global([data-smrt-component="Input"]),
3461
+ .search-filters :global([data-smrt-component="Select"]) {
3462
3462
  padding: 0.5rem 0.9rem;
3463
- border: 1px solid var(--smrt-color-outline);
3463
+ border: var(--smrt-hairline-strong);
3464
3464
  border-radius: 0.5rem;
3465
3465
  font-size: var(--smrt-typography-body-medium-size, 0.875rem);
3466
3466
  height: 38px;
@@ -3479,7 +3479,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
3479
3479
  background: var(--smrt-color-surface-container-low);
3480
3480
  border-radius: 0.5rem;
3481
3481
  padding: 0.25rem;
3482
- border: 1px solid var(--smrt-color-outline-variant);
3482
+ border: var(--smrt-hairline);
3483
3483
  }
3484
3484
 
3485
3485
  .view-toggles :global(.view-toggle-btn) {
@@ -3551,7 +3551,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
3551
3551
  gap: 0.75rem;
3552
3552
  margin-bottom: 1rem;
3553
3553
  padding: 0.75rem;
3554
- border: 1px solid var(--smrt-color-outline-variant);
3554
+ border: var(--smrt-hairline);
3555
3555
  border-radius: 0.65rem;
3556
3556
  background: var(--smrt-color-surface-container-low);
3557
3557
  }
@@ -3613,7 +3613,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
3613
3613
  background: var(--smrt-color-surface);
3614
3614
  border-radius: 0.75rem;
3615
3615
  padding: 1.5rem;
3616
- border: 1px solid var(--smrt-color-outline-variant);
3616
+ border: var(--smrt-hairline);
3617
3617
  transition: transform 0.2s, box-shadow 0.2s;
3618
3618
  display: flex;
3619
3619
  flex-direction: column;
@@ -3628,7 +3628,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
3628
3628
  width: calc(100% + 3rem);
3629
3629
  margin: -1.5rem -1.5rem 1rem -1.5rem;
3630
3630
  height: 160px;
3631
- border-bottom: 1px solid var(--smrt-color-outline-variant);
3631
+ border-bottom: var(--smrt-hairline);
3632
3632
  overflow: hidden;
3633
3633
  background: var(--smrt-color-surface-container-high);
3634
3634
  }
@@ -3675,7 +3675,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
3675
3675
 
3676
3676
  .status-published { background: var(--smrt-color-success-container); color: var(--smrt-color-on-success-container); border: 1px solid var(--smrt-color-success); }
3677
3677
  .status-draft { background: var(--smrt-color-warning-container); color: var(--smrt-color-on-warning-container); border: 1px solid var(--smrt-color-warning); }
3678
- .status-archived { background: var(--smrt-color-surface-container); color: var(--smrt-color-on-surface-variant); border: 1px solid var(--smrt-color-outline-variant); }
3678
+ .status-archived { background: var(--smrt-color-surface-container); color: var(--smrt-color-on-surface-variant); border: var(--smrt-hairline); }
3679
3679
 
3680
3680
  .state-highlighted { background: var(--smrt-color-warning-container); color: var(--smrt-color-on-warning-container); border: 1px solid var(--smrt-color-warning);}
3681
3681
  .state-active { background: var(--smrt-color-success-container); color: var(--smrt-color-on-success-container); border: 1px solid var(--smrt-color-success); }
@@ -3722,7 +3722,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
3722
3722
  display: flex;
3723
3723
  gap: 0.75rem;
3724
3724
  margin-top: 0.5rem;
3725
- border-top: 1px solid var(--smrt-color-outline-variant);
3725
+ border-top: var(--smrt-hairline);
3726
3726
  padding-top: 1rem;
3727
3727
  }
3728
3728
 
@@ -3730,7 +3730,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
3730
3730
  .content-actions a {
3731
3731
  flex: 1;
3732
3732
  padding: 0.5rem 1rem;
3733
- border: 1px solid var(--smrt-color-outline-variant);
3733
+ border: var(--smrt-hairline);
3734
3734
  border-radius: 0.375rem;
3735
3735
  background: var(--smrt-color-surface-container-low);
3736
3736
  color: var(--smrt-color-on-surface);
@@ -3745,7 +3745,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
3745
3745
  .content-actions :global(.content-action-btn:hover),
3746
3746
  .content-actions a:hover {
3747
3747
  background: var(--smrt-color-surface-variant);
3748
- border-color: var(--smrt-color-outline);
3748
+ border-color: var(--smrt-hairline-strong-color);
3749
3749
  }
3750
3750
 
3751
3751
  .content-actions .view-btn {
@@ -3773,7 +3773,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
3773
3773
  display: flex;
3774
3774
  flex-direction: column;
3775
3775
  gap: 0;
3776
- border-top: 1px solid var(--smrt-color-outline-variant);
3776
+ border-top: var(--smrt-hairline);
3777
3777
  }
3778
3778
 
3779
3779
  .content-row {
@@ -3782,7 +3782,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
3782
3782
  gap: 1.25rem;
3783
3783
  align-items: start;
3784
3784
  padding: 1.1rem 0;
3785
- border-bottom: 1px solid var(--smrt-color-outline-variant);
3785
+ border-bottom: var(--smrt-hairline);
3786
3786
  }
3787
3787
 
3788
3788
  .content-row__select {
@@ -3831,7 +3831,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
3831
3831
  min-height: 2.4rem;
3832
3832
  padding: 0 0.85rem;
3833
3833
  border-radius: var(--smrt-radius-full);
3834
- border: 1px solid var(--smrt-color-outline-variant);
3834
+ border: var(--smrt-hairline);
3835
3835
  background: transparent;
3836
3836
  color: var(--smrt-color-on-surface);
3837
3837
  font-weight: var(--smrt-typography-weight-semibold, 600);
@@ -3870,7 +3870,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
3870
3870
  .content-table-wrapper {
3871
3871
  background: var(--smrt-color-surface);
3872
3872
  border-radius: 0.75rem;
3873
- border: 1px solid var(--smrt-color-outline-variant);
3873
+ border: var(--smrt-hairline);
3874
3874
  overflow: hidden;
3875
3875
  box-shadow: var(--smrt-elevation-1, 0 1px 3px rgba(0,0,0,0.05));
3876
3876
  }
@@ -3913,7 +3913,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
3913
3913
  align-items: center;
3914
3914
  gap: 0.75rem;
3915
3915
  padding: 0.65rem 0.75rem;
3916
- border: 1px solid var(--smrt-color-outline-variant);
3916
+ border: var(--smrt-hairline);
3917
3917
  border-radius: 0.5rem;
3918
3918
  background: var(--smrt-color-surface-container-low);
3919
3919
  color: var(--smrt-color-on-surface-variant);
@@ -3997,7 +3997,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
3997
3997
  margin-bottom: 1rem;
3998
3998
  padding: 0.75rem 1rem;
3999
3999
  border-radius: 0.5rem;
4000
- border: 1px solid var(--smrt-color-outline-variant);
4000
+ border: var(--smrt-hairline);
4001
4001
  background: var(--smrt-color-surface-container-low);
4002
4002
  color: var(--smrt-color-on-surface-variant);
4003
4003
  font-size: var(--smrt-typography-body-medium-size, 0.875rem);
@@ -4015,7 +4015,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
4015
4015
  }
4016
4016
 
4017
4017
  .state-notice :global(.state-notice__dismiss) {
4018
- border: 1px solid var(--smrt-color-outline-variant);
4018
+ border: var(--smrt-hairline);
4019
4019
  border-radius: 0.375rem;
4020
4020
  padding: 0.25rem 0.75rem;
4021
4021
  cursor: pointer;
@@ -4056,7 +4056,7 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
4056
4056
  }
4057
4057
 
4058
4058
  .state-panel :global(.retry-button) {
4059
- border: 1px solid var(--smrt-color-outline);
4059
+ border: var(--smrt-hairline-strong);
4060
4060
  border-radius: 0.5rem;
4061
4061
  padding: 0.5rem 1rem;
4062
4062
  cursor: pointer;
@@ -4085,8 +4085,8 @@ const tableColumns: DataTableColumn<ContentListRow>[] = $derived([
4085
4085
  width: 100%;
4086
4086
  }
4087
4087
 
4088
- .search-filters :global(.input),
4089
- .search-filters :global(.select),
4088
+ .search-filters :global([data-smrt-component="Input"]),
4089
+ .search-filters :global([data-smrt-component="Select"]),
4090
4090
  .add-button {
4091
4091
  width: 100%;
4092
4092
  }