{"section":"testing","slug":"payload-contracts","featureTitle":"Payload Contract Testing","summary":"Run one project-level contract suite through the canonical test runner and let strategy profiles verify payload boundaries without hand-writing repetitive negative cases.","entryLine":"Testing in Semitexa can start from the transport boundary itself: payloads declare what should be verified, and the framework runs the strategy suite.","highlights":["#[TestablePayload]","test:run","StrictProfileStrategy","MonkeyTestingStrategy"],"learnMoreLabel":"See the testing workflow →","deepDiveLabel":"What the profiles actually buy you →","documentBodyHtml":"<article class=\"sx-docs-fragment\" data-doc-id=\"testing/payload-contracts\" data-doc-locale=\"en\">\n<h1>Payload Contract Testing</h1>\n<p>Automated contract testing for payloads -- <code>#[TestablePayload]</code> marks a payload for strategy-based validation.</p>\n<h2>How it works</h2>\n<p>The testing framework discovers testable payloads, applies strategy profiles (Standard, Strict, Paranoid), and runs security, type enforcement, and monkey testing strategies against each endpoint. The semitexa-testing package ships its own <code>ProjectPayloadsContractTest</code> integration test, and the canonical runner <code>bin/semitexa test:run</code> appends that suite automatically from <code>vendor/semitexa/testing/tests/Integration/ProjectPayloadsContractTest.php</code> in consuming projects. That keeps payload-contract coverage enabled without scaffolding a root-level <code>tests/</code> bucket, but it also means direct <code>vendor/bin/phpunit</code> runs are not the supported entry point.</p>\n<h2>Why this matters</h2>\n<p>Contract tests verify that payloads reject bad input and accept good input without writing individual test cases. Strategy profiles let teams choose their risk tolerance -- Strict catches common boundary mistakes, while Paranoid adds monkey input and deeper type mutation coverage.</p>\n</article>","sectionLabel":"Testing","navSections":[{"key":"get-started","label":"Start Here","sidebarLabel":"Get Started","summary":"The shortest path from fresh scaffold to a trustworthy local Semitexa runtime: install, boot, understand the module map, bind a real host, and reach the first tenant boundary.","icon":"GO","eyebrow":"Onboarding","starter":true,"prerequisites":[],"featureCount":7,"href":"/demo/get-started","features":[{"section":"get-started","slug":"module-structure","label":"Start Here","title":"Module Structure","summary":"The minimal Semitexa module is a typed HTTP spine of payload, handler, resource, and template.","opensInNewTab":false,"href":"/demo/get-started/module-structure"},{"section":"get-started","slug":"installation","label":"Start Here","title":"Installation","summary":"Create the project, review the baseline env contract, and bring up the Semitexa runtime the supported way.","opensInNewTab":false,"href":"/demo/get-started/installation"},{"section":"get-started","slug":"local-domain","label":"Start Here","title":"Local Domain","summary":"Register .test domains through the built-in local-domain helper instead of relying on ad hoc host setup.","opensInNewTab":false,"href":"/demo/get-started/local-domain"},{"section":"get-started","slug":"base-tenant","label":"Start Here","title":"Base Tenant","summary":"Establish one default tenant context early so tenant-aware behavior is visible before the rest of the application grows.","opensInNewTab":false,"href":"/demo/get-started/base-tenant"},{"section":"get-started","slug":"locale-setup","label":"Start Here","title":"Locale Setup","summary":"Configure the minimal Locale contract so translations and locale-aware rendering become explicit early.","opensInNewTab":false,"href":"/demo/get-started/locale-setup"},{"section":"get-started","slug":"ai-console","label":"Start Here","title":"AI Console","summary":"Use the Semitexa AI console as a command translation surface over real operator commands.","opensInNewTab":false,"href":"/demo/get-started/ai-console"},{"section":"get-started","slug":"beyond-controllers","label":"Start Here","title":"Beyond Controllers","summary":"Understand why Semitexa keeps transport, use case, and rendering as separate explicit responsibilities.","opensInNewTab":false,"href":"/demo/get-started/beyond-controllers"}],"groups":[{"key":"structure","label":"Module Structure","featureCount":1,"features":[{"section":"get-started","slug":"module-structure","label":"Start Here","title":"Module Structure","summary":"The minimal Semitexa module is a typed HTTP spine of payload, handler, resource, and template.","opensInNewTab":false,"href":"/demo/get-started/module-structure"}]},{"key":"onboarding","label":"Onboarding","featureCount":6,"features":[{"section":"get-started","slug":"installation","label":"Start Here","title":"Installation","summary":"Create the project, review the baseline env contract, and bring up the Semitexa runtime the supported way.","opensInNewTab":false,"href":"/demo/get-started/installation"},{"section":"get-started","slug":"local-domain","label":"Start Here","title":"Local Domain","summary":"Register .test domains through the built-in local-domain helper instead of relying on ad hoc host setup.","opensInNewTab":false,"href":"/demo/get-started/local-domain"},{"section":"get-started","slug":"base-tenant","label":"Start Here","title":"Base Tenant","summary":"Establish one default tenant context early so tenant-aware behavior is visible before the rest of the application grows.","opensInNewTab":false,"href":"/demo/get-started/base-tenant"},{"section":"get-started","slug":"locale-setup","label":"Start Here","title":"Locale Setup","summary":"Configure the minimal Locale contract so translations and locale-aware rendering become explicit early.","opensInNewTab":false,"href":"/demo/get-started/locale-setup"},{"section":"get-started","slug":"ai-console","label":"Start Here","title":"AI Console","summary":"Use the Semitexa AI console as a command translation surface over real operator commands.","opensInNewTab":false,"href":"/demo/get-started/ai-console"},{"section":"get-started","slug":"beyond-controllers","label":"Start Here","title":"Beyond Controllers","summary":"Understand why Semitexa keeps transport, use case, and rendering as separate explicit responsibilities.","opensInNewTab":false,"href":"/demo/get-started/beyond-controllers"}]}]},{"key":"routing","label":"Routing & Handlers","summary":"Attribute-driven routes, typed handlers, and content negotiation in one coherent request pipeline.","icon":"RT","eyebrow":"HTTP Layer","starter":true,"prerequisites":[],"featureCount":7,"href":"/demo/routing","features":[{"section":"routing","slug":"basic","label":"Routing & Handlers","title":"Basic Route","summary":"Define a route with one access attribute on the payload — no XML, no YAML, no config files.","opensInNewTab":false,"href":"/demo/routing/basic"},{"section":"routing","slug":"parameterized","label":"Routing & Handlers","title":"Parameterized Route","summary":"Path parameters with regex constraints and typed injection.","opensInNewTab":false,"href":"/demo/routing/parameterized/headphones"},{"section":"routing","slug":"env-route-override","label":"Routing & Handlers","title":"Env Route Override","summary":"Keep the payload as the route source of truth while allowing operations to remap the public URL through .env.","opensInNewTab":false,"href":"/demo/routing/env-route-override"},{"section":"routing","slug":"payload-shield","label":"Routing & Handlers","title":"Payload As A Shield","summary":"Hydration happens before the handler, and each setter owns the normalization and guard logic for its own field.","opensInNewTab":false,"href":"/demo/routing/payload-shield"},{"section":"routing","slug":"payload-parts","label":"Routing & Handlers","title":"Payload Parts","summary":"One module owns the route, another module can extend the same payload contract without forking or reopening the base class.","opensInNewTab":false,"href":"/demo/routing/payload-parts"},{"section":"routing","slug":"content-negotiation","label":"Routing & Handlers","title":"Content Negotiation","summary":"One endpoint, multiple response formats — automatically.","opensInNewTab":false,"href":"/demo/routing/content-negotiation"},{"section":"routing","slug":"public-endpoint","label":"Routing & Handlers","title":"Public Payload","summary":"Anonymous endpoints opt in explicitly with the public access attribute. Every other payload requires authentication.","opensInNewTab":false,"href":"/demo/routing/public-endpoint"}],"groups":[{"key":"foundations","label":"Foundations","featureCount":3,"features":[{"section":"routing","slug":"basic","label":"Routing & Handlers","title":"Basic Route","summary":"Define a route with one access attribute on the payload — no XML, no YAML, no config files.","opensInNewTab":false,"href":"/demo/routing/basic"},{"section":"routing","slug":"parameterized","label":"Routing & Handlers","title":"Parameterized Route","summary":"Path parameters with regex constraints and typed injection.","opensInNewTab":false,"href":"/demo/routing/parameterized/headphones"},{"section":"routing","slug":"env-route-override","label":"Routing & Handlers","title":"Env Route Override","summary":"Keep the payload as the route source of truth while allowing operations to remap the public URL through .env.","opensInNewTab":false,"href":"/demo/routing/env-route-override"}]},{"key":"request-model","label":"Request Model","featureCount":2,"features":[{"section":"routing","slug":"payload-shield","label":"Routing & Handlers","title":"Payload As A Shield","summary":"Hydration happens before the handler, and each setter owns the normalization and guard logic for its own field.","opensInNewTab":false,"href":"/demo/routing/payload-shield"},{"section":"routing","slug":"payload-parts","label":"Routing & Handlers","title":"Payload Parts","summary":"One module owns the route, another module can extend the same payload contract without forking or reopening the base class.","opensInNewTab":false,"href":"/demo/routing/payload-parts"}]},{"key":"delivery","label":"Delivery","featureCount":2,"features":[{"section":"routing","slug":"content-negotiation","label":"Routing & Handlers","title":"Content Negotiation","summary":"One endpoint, multiple response formats — automatically.","opensInNewTab":false,"href":"/demo/routing/content-negotiation"},{"section":"routing","slug":"public-endpoint","label":"Routing & Handlers","title":"Public Payload","summary":"Anonymous endpoints opt in explicitly with the public access attribute. Every other payload requires authentication.","opensInNewTab":false,"href":"/demo/routing/public-endpoint"}]}]},{"key":"di","label":"Dependency Injection","summary":"Single-path DI with explicit lifecycles, deterministic contracts, and stable boot behavior for long-running workers.","icon":"DI","eyebrow":"Container","starter":true,"prerequisites":[],"featureCount":5,"href":"/demo/di","features":[{"section":"di","slug":"overview","label":"Dependency Injection","title":"DI Canon","summary":"One canonical DI path for container-managed classes: explicit properties, explicit lifecycles, deterministic boot.","opensInNewTab":false,"href":"/demo/di/overview"},{"section":"di","slug":"readonly","label":"Dependency Injection","title":"Readonly Injection","summary":"One explicit DI path, one shared worker instance — fast at runtime and stable under reload.","opensInNewTab":false,"href":"/demo/di/readonly"},{"section":"di","slug":"mutable","label":"Dependency Injection","title":"Mutable Injection","summary":"Execution-scoped services get a fresh clone every run — safe state without contaminating the worker.","opensInNewTab":false,"href":"/demo/di/mutable"},{"section":"di","slug":"factory","label":"Dependency Injection","title":"Factory Injection","summary":"On-demand creation stays explicit — lazy instances without falling back to service locator habits.","opensInNewTab":false,"href":"/demo/di/factory"},{"section":"di","slug":"contracts","label":"Dependency Injection","title":"Service Contracts","summary":"Depend on contracts, but keep ownership explicit — deterministic substitution instead of runtime magic.","opensInNewTab":false,"href":"/demo/di/contracts"}],"groups":[{"key":"container-basics","label":"Container Basics","featureCount":5,"features":[{"section":"di","slug":"overview","label":"Dependency Injection","title":"DI Canon","summary":"One canonical DI path for container-managed classes: explicit properties, explicit lifecycles, deterministic boot.","opensInNewTab":false,"href":"/demo/di/overview"},{"section":"di","slug":"readonly","label":"Dependency Injection","title":"Readonly Injection","summary":"One explicit DI path, one shared worker instance — fast at runtime and stable under reload.","opensInNewTab":false,"href":"/demo/di/readonly"},{"section":"di","slug":"mutable","label":"Dependency Injection","title":"Mutable Injection","summary":"Execution-scoped services get a fresh clone every run — safe state without contaminating the worker.","opensInNewTab":false,"href":"/demo/di/mutable"},{"section":"di","slug":"factory","label":"Dependency Injection","title":"Factory Injection","summary":"On-demand creation stays explicit — lazy instances without falling back to service locator habits.","opensInNewTab":false,"href":"/demo/di/factory"},{"section":"di","slug":"contracts","label":"Dependency Injection","title":"Service Contracts","summary":"Depend on contracts, but keep ownership explicit — deterministic substitution instead of runtime magic.","opensInNewTab":false,"href":"/demo/di/contracts"}]}]},{"key":"data","label":"Persistence","summary":"Attribute-mapped resources, repositories, filtering, pagination, and relations with real demo data.","icon":"DB","eyebrow":"Persistence","starter":true,"prerequisites":[],"featureCount":9,"href":"/demo/data","features":[{"section":"data","slug":"domain-models","label":"Persistence","title":"Domain-Level Models","summary":"Semitexa separates persistence resources from business models. Resources map tables; domain models carry behavior and invariants.","opensInNewTab":false,"href":"/demo/data/domain-models"},{"section":"data","slug":"repository-workflow","label":"Persistence","title":"Repository Workflow","summary":"The canonical Semitexa path: handlers depend on repository contracts, repositories return domain models, and persistence resources stay behind the boundary.","opensInNewTab":false,"href":"/demo/data/repository-workflow"},{"section":"data","slug":"schema-sync","label":"Persistence","title":"Schema Sync, Not Migration Churn","summary":"Semitexa creates SQL only when the real schema changed, blocks destructive drops by default, and logs the exact DDL plan as SQL and JSON.","opensInNewTab":false,"href":"/demo/data/schema-sync"},{"section":"data","slug":"query","label":"Persistence","title":"Query Builder","summary":"Compose type-safe queries with a fluent API — no raw SQL, no magic strings.","opensInNewTab":false,"href":"/demo/data/query"},{"section":"data","slug":"filtering","label":"Persistence","title":"Filtering","summary":"Mark a property #[Filterable] and the ORM handles the rest — no manual WHERE clauses.","opensInNewTab":false,"href":"/demo/data/filtering"},{"section":"data","slug":"pagination","label":"Persistence","title":"Pagination","summary":"Offset and cursor pagination out of the box — switch modes with a single query parameter.","opensInNewTab":false,"href":"/demo/data/pagination"},{"section":"data","slug":"relations","label":"Persistence","title":"Relations","summary":"Declare parent and child links on the resource itself, then read typed relations from the handler.","opensInNewTab":false,"href":"/demo/data/relations"},{"section":"data","slug":"table-extension","label":"Persistence","title":"Shared Table Extension","summary":"Two modules can extend one table independently, and the ORM merges the schema without forcing either side to edit the other.","opensInNewTab":false,"href":"/demo/data/table-extension"},{"section":"data","slug":"n-plus-one","label":"Persistence","title":"N+1 Without Magic","summary":"Semitexa avoids N+1 by using resource slices for the exact columns and relations each screen needs, instead of hiding database traffic behind implicit relation loading.","opensInNewTab":false,"href":"/demo/data/n-plus-one"}],"groups":[{"key":"modeling","label":"Modeling & Workflow","featureCount":3,"features":[{"section":"data","slug":"domain-models","label":"Persistence","title":"Domain-Level Models","summary":"Semitexa separates persistence resources from business models. Resources map tables; domain models carry behavior and invariants.","opensInNewTab":false,"href":"/demo/data/domain-models"},{"section":"data","slug":"repository-workflow","label":"Persistence","title":"Repository Workflow","summary":"The canonical Semitexa path: handlers depend on repository contracts, repositories return domain models, and persistence resources stay behind the boundary.","opensInNewTab":false,"href":"/demo/data/repository-workflow"},{"section":"data","slug":"schema-sync","label":"Persistence","title":"Schema Sync, Not Migration Churn","summary":"Semitexa creates SQL only when the real schema changed, blocks destructive drops by default, and logs the exact DDL plan as SQL and JSON.","opensInNewTab":false,"href":"/demo/data/schema-sync"}]},{"key":"querying","label":"Querying","featureCount":6,"features":[{"section":"data","slug":"query","label":"Persistence","title":"Query Builder","summary":"Compose type-safe queries with a fluent API — no raw SQL, no magic strings.","opensInNewTab":false,"href":"/demo/data/query"},{"section":"data","slug":"filtering","label":"Persistence","title":"Filtering","summary":"Mark a property #[Filterable] and the ORM handles the rest — no manual WHERE clauses.","opensInNewTab":false,"href":"/demo/data/filtering"},{"section":"data","slug":"pagination","label":"Persistence","title":"Pagination","summary":"Offset and cursor pagination out of the box — switch modes with a single query parameter.","opensInNewTab":false,"href":"/demo/data/pagination"},{"section":"data","slug":"relations","label":"Persistence","title":"Relations","summary":"Declare parent and child links on the resource itself, then read typed relations from the handler.","opensInNewTab":false,"href":"/demo/data/relations"},{"section":"data","slug":"table-extension","label":"Persistence","title":"Shared Table Extension","summary":"Two modules can extend one table independently, and the ORM merges the schema without forcing either side to edit the other.","opensInNewTab":false,"href":"/demo/data/table-extension"},{"section":"data","slug":"n-plus-one","label":"Persistence","title":"N+1 Without Magic","summary":"Semitexa avoids N+1 by using resource slices for the exact columns and relations each screen needs, instead of hiding database traffic behind implicit relation loading.","opensInNewTab":false,"href":"/demo/data/n-plus-one"}]}]},{"key":"auth","label":"Security","summary":"Typed session payloads, machine credentials, RBAC, and route protection without string-key auth chaos.","icon":"AU","eyebrow":"Security","starter":false,"prerequisites":[],"featureCount":7,"href":"/demo/auth","features":[{"section":"auth","slug":"session","label":"Security","title":"Session Auth","summary":"Google signs the user in, then the session stores the selected demo role and re-hydrates it on every request.","opensInNewTab":false,"href":"/demo/auth/session"},{"section":"auth","slug":"session-payloads","label":"Security","title":"Session Payloads","summary":"Semitexa forbids string-key session chaos: session state lives in typed Session Payloads or it does not exist.","opensInNewTab":false,"href":"/demo/auth/session-payloads"},{"section":"auth","slug":"google","label":"Security","title":"Google Authorization","summary":"Authorization is required for demo SSE blocks that keep a long-lived backend connection open.","opensInNewTab":false,"href":"/demo/auth/google"},{"section":"auth","slug":"machine","label":"Security","title":"Machine Auth","summary":"Service-to-service authentication via Bearer tokens — scoped, revocable, and audited.","opensInNewTab":false,"href":"/demo/auth/machine"},{"section":"auth","slug":"protected","label":"Security","title":"Protected Route","summary":"Add one access attribute and one optional permission attribute and the framework enforces access — 401 for unauthenticated requests, 403 for unauthorized ones.","opensInNewTab":false,"href":"/demo/auth/protected"},{"section":"auth","slug":"requires-permission","label":"Security","title":"Requires Permission","summary":"Declare one permission slug on the payload and let the framework enforce it before your handler runs.","opensInNewTab":false,"href":"/demo/auth/requires-permission"},{"section":"auth","slug":"rbac","label":"Security","title":"RBAC","summary":"Hybrid RBAC with coarse-grained capabilities, exact permission slugs, and module-owned permission catalogs.","opensInNewTab":false,"href":"/demo/auth/rbac"}],"groups":[{"key":"identity","label":"Identity","featureCount":4,"features":[{"section":"auth","slug":"session","label":"Security","title":"Session Auth","summary":"Google signs the user in, then the session stores the selected demo role and re-hydrates it on every request.","opensInNewTab":false,"href":"/demo/auth/session"},{"section":"auth","slug":"session-payloads","label":"Security","title":"Session Payloads","summary":"Semitexa forbids string-key session chaos: session state lives in typed Session Payloads or it does not exist.","opensInNewTab":false,"href":"/demo/auth/session-payloads"},{"section":"auth","slug":"google","label":"Security","title":"Google Authorization","summary":"Authorization is required for demo SSE blocks that keep a long-lived backend connection open.","opensInNewTab":false,"href":"/demo/auth/google"},{"section":"auth","slug":"machine","label":"Security","title":"Machine Auth","summary":"Service-to-service authentication via Bearer tokens — scoped, revocable, and audited.","opensInNewTab":false,"href":"/demo/auth/machine"}]},{"key":"access-control","label":"Access Control","featureCount":3,"features":[{"section":"auth","slug":"protected","label":"Security","title":"Protected Route","summary":"Add one access attribute and one optional permission attribute and the framework enforces access — 401 for unauthenticated requests, 403 for unauthorized ones.","opensInNewTab":false,"href":"/demo/auth/protected"},{"section":"auth","slug":"requires-permission","label":"Security","title":"Requires Permission","summary":"Declare one permission slug on the payload and let the framework enforce it before your handler runs.","opensInNewTab":false,"href":"/demo/auth/requires-permission"},{"section":"auth","slug":"rbac","label":"Security","title":"RBAC","summary":"Hybrid RBAC with coarse-grained capabilities, exact permission slugs, and module-owned permission catalogs.","opensInNewTab":false,"href":"/demo/auth/rbac"}]}]},{"key":"events","label":"Async","summary":"Synchronous and deferred event flows, queues, and SSE-style interactions.","icon":"EV","eyebrow":"Async","starter":false,"prerequisites":[],"featureCount":5,"href":"/demo/events","features":[{"section":"events","slug":"arena","label":"Async","title":"Execution Arena","summary":"Launch the same backend intent in sync, Swoole async, and queued modes, then watch the proof arrive over SSE.","opensInNewTab":false,"href":"/demo/events/arena"},{"section":"events","slug":"sync","label":"Async","title":"Sync Events","summary":"Dispatch an event and all sync listeners run before the response is sent.","opensInNewTab":false,"href":"/demo/events/sync"},{"section":"events","slug":"deferred","label":"Async","title":"Deferred Handler","summary":"Heavy work runs after the response is sent — the user gets instant feedback.","opensInNewTab":false,"href":"/demo/events/deferred"},{"section":"events","slug":"queued","label":"Async","title":"Queued Handler","summary":"Events survive restarts and scale across workers — backed by a durable message queue.","opensInNewTab":false,"href":"/demo/events/queued"},{"section":"events","slug":"sse","label":"Async","title":"SSE Stream","summary":"Real-time server push without WebSockets — connect once and receive real backend events over plain HTTP.","opensInNewTab":false,"href":"/demo/events/sse"}],"groups":[{"key":"event-flow","label":"Event Flow","featureCount":5,"features":[{"section":"events","slug":"arena","label":"Async","title":"Execution Arena","summary":"Launch the same backend intent in sync, Swoole async, and queued modes, then watch the proof arrive over SSE.","opensInNewTab":false,"href":"/demo/events/arena"},{"section":"events","slug":"sync","label":"Async","title":"Sync Events","summary":"Dispatch an event and all sync listeners run before the response is sent.","opensInNewTab":false,"href":"/demo/events/sync"},{"section":"events","slug":"deferred","label":"Async","title":"Deferred Handler","summary":"Heavy work runs after the response is sent — the user gets instant feedback.","opensInNewTab":false,"href":"/demo/events/deferred"},{"section":"events","slug":"queued","label":"Async","title":"Queued Handler","summary":"Events survive restarts and scale across workers — backed by a durable message queue.","opensInNewTab":false,"href":"/demo/events/queued"},{"section":"events","slug":"sse","label":"Async","title":"SSE Stream","summary":"Real-time server push without WebSockets — connect once and receive real backend events over plain HTTP.","opensInNewTab":false,"href":"/demo/events/sse"}]}]},{"key":"rendering","label":"UI Rendering & SSR","summary":"One rendering story from handler to HTML: page data, page regions, and live updates stay in the same server-driven model instead of splitting into frontend and backend template logic.","icon":"UI","eyebrow":"Frontend","starter":false,"prerequisites":[],"featureCount":15,"href":"/demo/rendering","features":[{"section":"rendering","slug":"philosophy","label":"UI Rendering & SSR","title":"SSR Philosophy","summary":"Semitexa SSR is one continuous rendering architecture: page, slots, deferred regions, live refresh, and interactive components stay inside one server-owned story.","opensInNewTab":false,"href":"/demo/rendering/philosophy"},{"section":"rendering","slug":"resource-dtos","label":"UI Rendering & SSR","title":"Resource DTOs","summary":"A Resource DTO is the one typed source of presentation data: handlers shape it once, templates consume it everywhere, and no view has to dissect random arrays.","opensInNewTab":false,"href":"/demo/rendering/resource-dtos"},{"section":"rendering","slug":"slots","label":"UI Rendering & SSR","title":"Slot Resources","summary":"Each page region is its own resource pipeline with the same template system as the main page — no scattered partial glue, no mystery wiring.","opensInNewTab":false,"href":"/demo/rendering/slots"},{"section":"rendering","slug":"components","label":"UI Rendering & SSR","title":"Components","summary":"Reusable, attribute-registered UI components — discovered automatically from the classmap.","opensInNewTab":false,"href":"/demo/rendering/components"},{"section":"rendering","slug":"seo","label":"UI Rendering & SSR","title":"SEO","summary":"Set title, description, and Open Graph tags from your handler — no template hacks needed.","opensInNewTab":false,"href":"/demo/rendering/seo"},{"section":"rendering","slug":"assets","label":"UI Rendering & SSR","title":"Asset Pipeline","summary":"Declare assets with glob patterns in assets.json — served, versioned, and injected automatically.","opensInNewTab":false,"href":"/demo/rendering/assets"},{"section":"rendering","slug":"component-scripts","label":"UI Rendering & SSR","title":"Component Script Assets","summary":"A Semitexa SSR component can own its optional enhancement asset, so behavior travels with the component instead of leaking into page-level glue.","opensInNewTab":false,"href":"/demo/rendering/component-scripts"},{"section":"rendering","slug":"deferred-scripts","label":"UI Rendering & SSR","title":"Script Injection","summary":"Deferred blocks carry their own JS — injected once when the block arrives, never duplicated.","opensInNewTab":false,"href":"/demo/rendering/deferred-scripts"},{"section":"rendering","slug":"deferred","label":"UI Rendering & SSR","title":"Deferred Blocks","summary":"SSR renders the shell first, then expensive regions stream in as real HTML over SSE — no SPA handoff and no client-side page rebuild.","opensInNewTab":true,"href":"/demo/rendering/deferred"},{"section":"rendering","slug":"deferred-encapsulation","label":"UI Rendering & SSR","title":"Block Isolation","summary":"Two identical blocks on the same page run independently — scoped DOM, scoped JS, no conflicts.","opensInNewTab":false,"href":"/demo/rendering/deferred-encapsulation"},{"section":"rendering","slug":"deferred-live","label":"UI Rendering & SSR","title":"Live Widgets","summary":"A live slot can refresh itself on a timer while the page stays SSR-first — no SPA runtime and no handwritten polling layer.","opensInNewTab":false,"href":"/demo/rendering/deferred-live"},{"section":"rendering","slug":"reactive-report","label":"UI Rendering & SSR","title":"Reactive Report","summary":"Background work updates an SSR-first slot in place, so the UI feels live without falling back to SPA state orchestration.","opensInNewTab":false,"href":"/demo/rendering/reactive-report"},{"section":"rendering","slug":"reactive-import","label":"UI Rendering & SSR","title":"Reactive Import","summary":"Background batches keep moving, and the page reflects server progress as live HTML instead of a client-managed progress app.","opensInNewTab":false,"href":"/demo/rendering/reactive-import"},{"section":"rendering","slug":"reactive-analytics","label":"UI Rendering & SSR","title":"Reactive Analytics","summary":"Independent analytics jobs can light up one dashboard progressively, while the page stays server-rendered from the first byte.","opensInNewTab":false,"href":"/demo/rendering/reactive-analytics"},{"section":"rendering","slug":"reactive-ai","label":"UI Rendering & SSR","title":"Reactive AI Task","summary":"Submit a task and watch the AI pipeline stages reveal one by one as the cron job processes it.","opensInNewTab":false,"href":"/demo/rendering/reactive-ai"}],"groups":[{"key":"rendering-model","label":"SSR Foundation","featureCount":8,"features":[{"section":"rendering","slug":"philosophy","label":"UI Rendering & SSR","title":"SSR Philosophy","summary":"Semitexa SSR is one continuous rendering architecture: page, slots, deferred regions, live refresh, and interactive components stay inside one server-owned story.","opensInNewTab":false,"href":"/demo/rendering/philosophy"},{"section":"rendering","slug":"resource-dtos","label":"UI Rendering & SSR","title":"Resource DTOs","summary":"A Resource DTO is the one typed source of presentation data: handlers shape it once, templates consume it everywhere, and no view has to dissect random arrays.","opensInNewTab":false,"href":"/demo/rendering/resource-dtos"},{"section":"rendering","slug":"slots","label":"UI Rendering & SSR","title":"Slot Resources","summary":"Each page region is its own resource pipeline with the same template system as the main page — no scattered partial glue, no mystery wiring.","opensInNewTab":false,"href":"/demo/rendering/slots"},{"section":"rendering","slug":"components","label":"UI Rendering & SSR","title":"Components","summary":"Reusable, attribute-registered UI components — discovered automatically from the classmap.","opensInNewTab":false,"href":"/demo/rendering/components"},{"section":"rendering","slug":"seo","label":"UI Rendering & SSR","title":"SEO","summary":"Set title, description, and Open Graph tags from your handler — no template hacks needed.","opensInNewTab":false,"href":"/demo/rendering/seo"},{"section":"rendering","slug":"assets","label":"UI Rendering & SSR","title":"Asset Pipeline","summary":"Declare assets with glob patterns in assets.json — served, versioned, and injected automatically.","opensInNewTab":false,"href":"/demo/rendering/assets"},{"section":"rendering","slug":"component-scripts","label":"UI Rendering & SSR","title":"Component Script Assets","summary":"A Semitexa SSR component can own its optional enhancement asset, so behavior travels with the component instead of leaking into page-level glue.","opensInNewTab":false,"href":"/demo/rendering/component-scripts"},{"section":"rendering","slug":"deferred-scripts","label":"UI Rendering & SSR","title":"Script Injection","summary":"Deferred blocks carry their own JS — injected once when the block arrives, never duplicated.","opensInNewTab":false,"href":"/demo/rendering/deferred-scripts"}]},{"key":"deferred","label":"Deferred Delivery","featureCount":2,"features":[{"section":"rendering","slug":"deferred","label":"UI Rendering & SSR","title":"Deferred Blocks","summary":"SSR renders the shell first, then expensive regions stream in as real HTML over SSE — no SPA handoff and no client-side page rebuild.","opensInNewTab":true,"href":"/demo/rendering/deferred"},{"section":"rendering","slug":"deferred-encapsulation","label":"UI Rendering & SSR","title":"Block Isolation","summary":"Two identical blocks on the same page run independently — scoped DOM, scoped JS, no conflicts.","opensInNewTab":false,"href":"/demo/rendering/deferred-encapsulation"}]},{"key":"live","label":"Reactive UI","featureCount":5,"features":[{"section":"rendering","slug":"deferred-live","label":"UI Rendering & SSR","title":"Live Widgets","summary":"A live slot can refresh itself on a timer while the page stays SSR-first — no SPA runtime and no handwritten polling layer.","opensInNewTab":false,"href":"/demo/rendering/deferred-live"},{"section":"rendering","slug":"reactive-report","label":"UI Rendering & SSR","title":"Reactive Report","summary":"Background work updates an SSR-first slot in place, so the UI feels live without falling back to SPA state orchestration.","opensInNewTab":false,"href":"/demo/rendering/reactive-report"},{"section":"rendering","slug":"reactive-import","label":"UI Rendering & SSR","title":"Reactive Import","summary":"Background batches keep moving, and the page reflects server progress as live HTML instead of a client-managed progress app.","opensInNewTab":false,"href":"/demo/rendering/reactive-import"},{"section":"rendering","slug":"reactive-analytics","label":"UI Rendering & SSR","title":"Reactive Analytics","summary":"Independent analytics jobs can light up one dashboard progressively, while the page stays server-rendered from the first byte.","opensInNewTab":false,"href":"/demo/rendering/reactive-analytics"},{"section":"rendering","slug":"reactive-ai","label":"UI Rendering & SSR","title":"Reactive AI Task","summary":"Submit a task and watch the AI pipeline stages reveal one by one as the cron job processes it.","opensInNewTab":false,"href":"/demo/rendering/reactive-ai"}]}]},{"key":"platform","label":"Tenancy","summary":"Multi-tenant resolution, tenant-aware configuration, and strict isolation of data and background work.","icon":"TN","eyebrow":"Multi-Tenant","starter":false,"prerequisites":["data","rendering"],"featureCount":5,"href":"/demo/platform","features":[{"section":"platform","slug":"tenancy-resolution","label":"Tenancy","title":"Tenant Context Resolution","summary":"See how Semitexa resolves the active tenant from subdomain, header, path, or query input before the rest of the platform runs.","opensInNewTab":false,"href":"/demo/platform/tenancy-resolution"},{"section":"platform","slug":"tenancy-config","label":"Tenancy","title":"Per-Tenant Configuration","summary":"Three demo tenants with distinct branding -- switch tenant, everything changes without if/else.","opensInNewTab":false,"href":"/demo/platform/tenancy-config"},{"section":"platform","slug":"tenancy-layers","label":"Tenancy","title":"Multi-Layer Tenancy","summary":"Organization, Locale, Theme, Environment -- four independent layers compose into one TenantContext.","opensInNewTab":false,"href":"/demo/platform/tenancy-layers"},{"section":"platform","slug":"tenancy-isolation","label":"Tenancy","title":"Data Isolation","summary":"Product listing scoped by tenant -- switch tenant, list changes. Zero manual WHERE clauses.","opensInNewTab":false,"href":"/demo/platform/tenancy-isolation"},{"section":"platform","slug":"tenancy-queue","label":"Tenancy","title":"Queue Tenant Propagation","summary":"Tenant context travels with queued jobs -- _tenant key injected automatically, restored by worker.","opensInNewTab":false,"href":"/demo/platform/tenancy-queue"}],"groups":[{"key":"resolution","label":"Tenant Resolution","featureCount":1,"features":[{"section":"platform","slug":"tenancy-resolution","label":"Tenancy","title":"Tenant Context Resolution","summary":"See how Semitexa resolves the active tenant from subdomain, header, path, or query input before the rest of the platform runs.","opensInNewTab":false,"href":"/demo/platform/tenancy-resolution"}]},{"key":"configuration","label":"Tenant Configuration","featureCount":2,"features":[{"section":"platform","slug":"tenancy-config","label":"Tenancy","title":"Per-Tenant Configuration","summary":"Three demo tenants with distinct branding -- switch tenant, everything changes without if/else.","opensInNewTab":false,"href":"/demo/platform/tenancy-config"},{"section":"platform","slug":"tenancy-layers","label":"Tenancy","title":"Multi-Layer Tenancy","summary":"Organization, Locale, Theme, Environment -- four independent layers compose into one TenantContext.","opensInNewTab":false,"href":"/demo/platform/tenancy-layers"}]},{"key":"isolation","label":"Isolation & Work","featureCount":2,"features":[{"section":"platform","slug":"tenancy-isolation","label":"Tenancy","title":"Data Isolation","summary":"Product listing scoped by tenant -- switch tenant, list changes. Zero manual WHERE clauses.","opensInNewTab":false,"href":"/demo/platform/tenancy-isolation"},{"section":"platform","slug":"tenancy-queue","label":"Tenancy","title":"Queue Tenant Propagation","summary":"Tenant context travels with queued jobs -- _tenant key injected automatically, restored by worker.","opensInNewTab":false,"href":"/demo/platform/tenancy-queue"}]}]},{"key":"api","label":"API","summary":"External API endpoints, machine auth, versioning, and consumer-facing schema behavior.","icon":"API","eyebrow":"Machine","starter":false,"prerequisites":["routing","auth"],"featureCount":7,"href":"/demo/api","features":[{"section":"api","slug":"rest-api","label":"API","title":"REST API","summary":"Classic Semitexa REST endpoints with typed payloads, versioning, and consumer-friendly response shaping.","opensInNewTab":false,"href":"/demo/api/rest-api"},{"section":"api","slug":"structured-errors","label":"API","title":"Structured Errors","summary":"Throw domain exceptions and let semitexa-api map them into stable machine-readable error envelopes.","opensInNewTab":false,"href":"/demo/api/structured-errors"},{"section":"api","slug":"active-version","label":"API","title":"Active Version","summary":"The current collection endpoint with a clean X-Api-Version header and no deprecation noise.","opensInNewTab":false,"href":"/demo/api/active-version"},{"section":"api","slug":"sunset-version","label":"API","title":"Sunset Version","summary":"A deprecated product endpoint that emits both Deprecation and Sunset headers.","opensInNewTab":false,"href":"/demo/api/sunset-version"},{"section":"api","slug":"schema-discovery","label":"API","title":"Schema Discovery","summary":"A mini Swagger-style explorer for the live product API contract, schema endpoint, and response shapes.","opensInNewTab":false,"href":"/demo/api/schema-discovery"},{"section":"api","slug":"graphql","label":"API","title":"GraphQL API","summary":"GraphQL-first Semitexa contracts built with typed payloads and typed output DTOs instead of resolver sprawl.","opensInNewTab":false,"href":"/demo/api/graphql"},{"section":"api","slug":"rest-graphql","label":"API","title":"REST + GraphQL","summary":"One Semitexa use case can serve both REST and GraphQL without duplicating handler logic into separate resolver classes.","opensInNewTab":false,"href":"/demo/api/rest-graphql"}],"groups":[{"key":"public-api","label":"REST Surface","featureCount":4,"features":[{"section":"api","slug":"rest-api","label":"API","title":"REST API","summary":"Classic Semitexa REST endpoints with typed payloads, versioning, and consumer-friendly response shaping.","opensInNewTab":false,"href":"/demo/api/rest-api"},{"section":"api","slug":"structured-errors","label":"API","title":"Structured Errors","summary":"Throw domain exceptions and let semitexa-api map them into stable machine-readable error envelopes.","opensInNewTab":false,"href":"/demo/api/structured-errors"},{"section":"api","slug":"active-version","label":"API","title":"Active Version","summary":"The current collection endpoint with a clean X-Api-Version header and no deprecation noise.","opensInNewTab":false,"href":"/demo/api/active-version"},{"section":"api","slug":"sunset-version","label":"API","title":"Sunset Version","summary":"A deprecated product endpoint that emits both Deprecation and Sunset headers.","opensInNewTab":false,"href":"/demo/api/sunset-version"}]},{"key":"schema","label":"Schema Discovery","featureCount":3,"features":[{"section":"api","slug":"schema-discovery","label":"API","title":"Schema Discovery","summary":"A mini Swagger-style explorer for the live product API contract, schema endpoint, and response shapes.","opensInNewTab":false,"href":"/demo/api/schema-discovery"},{"section":"api","slug":"graphql","label":"API","title":"GraphQL API","summary":"GraphQL-first Semitexa contracts built with typed payloads and typed output DTOs instead of resolver sprawl.","opensInNewTab":false,"href":"/demo/api/graphql"},{"section":"api","slug":"rest-graphql","label":"API","title":"REST + GraphQL","summary":"One Semitexa use case can serve both REST and GraphQL without duplicating handler logic into separate resolver classes.","opensInNewTab":false,"href":"/demo/api/rest-graphql"}]}]},{"key":"cli","label":"CLI","summary":"Operational, introspection, and AI-oriented command surfaces that explain and drive the framework from the terminal.","icon":"CLI","eyebrow":"Operations","starter":false,"prerequisites":["routing"],"featureCount":6,"href":"/demo/cli","features":[{"section":"cli","slug":"describe-commands","label":"CLI","title":"Project Graph Introspection","summary":"Routes, modules, contracts, and handlers can be introspected directly from the CLI instead of reverse-engineering the framework graph by hand.","opensInNewTab":false,"href":"/demo/cli/describe-commands"},{"section":"cli","slug":"runtime-maintenance","label":"CLI","title":"Runtime Maintenance","summary":"Reload workers, clear stale cache, sync registries, lint architecture rules, and probe handler wiring without reaching for ad-hoc shell scripts.","opensInNewTab":false,"href":"/demo/cli/runtime-maintenance"},{"section":"cli","slug":"scaffolding-generators","label":"CLI","title":"Scaffolding Generators","summary":"Scaffold modules, pages, payloads, services, and contracts through commands that already understand Semitexa structure and AI-friendly output modes.","opensInNewTab":false,"href":"/demo/cli/scaffolding-generators"},{"section":"cli","slug":"workers-scheduling","label":"CLI","title":"Workers & Scheduling","summary":"Run queues, scheduler pools, mail delivery, webhooks, and tenant-scoped commands from a coherent operator surface instead of bespoke daemons.","opensInNewTab":false,"href":"/demo/cli/workers-scheduling"},{"section":"cli","slug":"ai-tooling","label":"CLI","title":"AI Tooling Surface","summary":"Semitexa exposes AI-facing commands as explicit CLI contracts: capabilities, skills, log access, and a local assistant entrypoint.","opensInNewTab":false,"href":"/demo/cli/ai-tooling"},{"section":"cli","slug":"orm-console","label":"CLI","title":"ORM Console Toolkit","summary":"The ORM ships with a practical CLI surface: status, diff, sync, and seed commands with dry-run safety and SQL plan export.","opensInNewTab":false,"href":"/demo/cli/orm-console"}],"groups":[{"key":"inspection","label":"Describe & Inspect","featureCount":2,"features":[{"section":"cli","slug":"describe-commands","label":"CLI","title":"Project Graph Introspection","summary":"Routes, modules, contracts, and handlers can be introspected directly from the CLI instead of reverse-engineering the framework graph by hand.","opensInNewTab":false,"href":"/demo/cli/describe-commands"},{"section":"cli","slug":"runtime-maintenance","label":"CLI","title":"Runtime Maintenance","summary":"Reload workers, clear stale cache, sync registries, lint architecture rules, and probe handler wiring without reaching for ad-hoc shell scripts.","opensInNewTab":false,"href":"/demo/cli/runtime-maintenance"}]},{"key":"automation","label":"Automation","featureCount":4,"features":[{"section":"cli","slug":"scaffolding-generators","label":"CLI","title":"Scaffolding Generators","summary":"Scaffold modules, pages, payloads, services, and contracts through commands that already understand Semitexa structure and AI-friendly output modes.","opensInNewTab":false,"href":"/demo/cli/scaffolding-generators"},{"section":"cli","slug":"workers-scheduling","label":"CLI","title":"Workers & Scheduling","summary":"Run queues, scheduler pools, mail delivery, webhooks, and tenant-scoped commands from a coherent operator surface instead of bespoke daemons.","opensInNewTab":false,"href":"/demo/cli/workers-scheduling"},{"section":"cli","slug":"ai-tooling","label":"CLI","title":"AI Tooling Surface","summary":"Semitexa exposes AI-facing commands as explicit CLI contracts: capabilities, skills, log access, and a local assistant entrypoint.","opensInNewTab":false,"href":"/demo/cli/ai-tooling"},{"section":"cli","slug":"orm-console","label":"CLI","title":"ORM Console Toolkit","summary":"The ORM ships with a practical CLI surface: status, diff, sync, and seed commands with dry-run safety and SQL plan export.","opensInNewTab":false,"href":"/demo/cli/orm-console"}]}]},{"key":"llm","label":"LLM Module","sidebarLabel":"LLM","summary":"The dedicated `semitexa/llm` module: AI assistant entrypoint, skill discovery, planner, executor, provider backends, and skill authoring rules.","icon":"AI","eyebrow":"semitexa/llm","starter":false,"prerequisites":["cli"],"featureCount":4,"href":"/demo/llm","features":[{"section":"llm","slug":"overview","label":"LLM Module","title":"LLM Module Overview","summary":"What `semitexa/llm` adds to the framework and how your project can expose its own CLI skills to the assistant.","opensInNewTab":false,"href":"/demo/llm/overview"},{"section":"llm","slug":"providers","label":"LLM Module","title":"Providers & Backends","summary":"Provider contracts, backend resolution, local vs remote Ollama, and the environment knobs that shape LLM runtime behavior.","opensInNewTab":false,"href":"/demo/llm/providers"},{"section":"llm","slug":"skills","label":"LLM Module","title":"Adding Skills","summary":"How a console command becomes AI-executable through `#[AsAiSkill]`, metadata policy, and registry discovery.","opensInNewTab":false,"href":"/demo/llm/skills"},{"section":"llm","slug":"execution-flow","label":"LLM Module","title":"Execution Flow","summary":"How a user request becomes a planner decision, a reviewed skill proposal, and finally a real console execution.","opensInNewTab":false,"href":"/demo/llm/execution-flow"}],"groups":[{"key":"assistant-basics","label":"Assistant Surface","featureCount":2,"features":[{"section":"llm","slug":"overview","label":"LLM Module","title":"LLM Module Overview","summary":"What `semitexa/llm` adds to the framework and how your project can expose its own CLI skills to the assistant.","opensInNewTab":false,"href":"/demo/llm/overview"},{"section":"llm","slug":"providers","label":"LLM Module","title":"Providers & Backends","summary":"Provider contracts, backend resolution, local vs remote Ollama, and the environment knobs that shape LLM runtime behavior.","opensInNewTab":false,"href":"/demo/llm/providers"}]},{"key":"skill-system","label":"Skill System","featureCount":2,"features":[{"section":"llm","slug":"skills","label":"LLM Module","title":"Adding Skills","summary":"How a console command becomes AI-executable through `#[AsAiSkill]`, metadata policy, and registry discovery.","opensInNewTab":false,"href":"/demo/llm/skills"},{"section":"llm","slug":"execution-flow","label":"LLM Module","title":"Execution Flow","summary":"How a user request becomes a planner decision, a reviewed skill proposal, and finally a real console execution.","opensInNewTab":false,"href":"/demo/llm/execution-flow"}]}]},{"key":"project-graph","label":"Project Graph","summary":"The `semitexa-project-graph` package: stored structural graph, intelligence layer, impact analysis, and task-scoped context for serious repository work.","icon":"PG","eyebrow":"AI Accelerator","starter":true,"prerequisites":["cli"],"featureCount":3,"href":"/demo/project-graph","features":[{"section":"project-graph","slug":"overview","label":"Project Graph","title":"Project Graph Overview","summary":"Understand what `semitexa-project-graph` adds: a stored structural map, an intelligence layer, and task-scoped context for large-codebase work.","opensInNewTab":false,"href":"/demo/project-graph/overview"},{"section":"project-graph","slug":"inspection","label":"Project Graph","title":"Inspecting the Graph","summary":"Use Project Graph queries and intelligence views to inspect modules, dependencies, flows, events, and hotspots without reconstructing the repository manually.","opensInNewTab":false,"href":"/demo/project-graph/inspection"},{"section":"project-graph","slug":"impact","label":"Project Graph","title":"Impact, Context, and Watch Mode","summary":"Use impact analysis, context packing, and watch mode to scope risky changes and keep graph-backed answers current during long work sessions.","opensInNewTab":false,"href":"/demo/project-graph/impact"}],"groups":[{"key":"launch","label":"Start Here","featureCount":1,"features":[{"section":"project-graph","slug":"overview","label":"Project Graph","title":"Project Graph Overview","summary":"Understand what `semitexa-project-graph` adds: a stored structural map, an intelligence layer, and task-scoped context for large-codebase work.","opensInNewTab":false,"href":"/demo/project-graph/overview"}]},{"key":"exploration","label":"Explore & Inspect","featureCount":1,"features":[{"section":"project-graph","slug":"inspection","label":"Project Graph","title":"Inspecting the Graph","summary":"Use Project Graph queries and intelligence views to inspect modules, dependencies, flows, events, and hotspots without reconstructing the repository manually.","opensInNewTab":false,"href":"/demo/project-graph/inspection"}]},{"key":"change-safety","label":"Impact & Context","featureCount":1,"features":[{"section":"project-graph","slug":"impact","label":"Project Graph","title":"Impact, Context, and Watch Mode","summary":"Use impact analysis, context packing, and watch mode to scope risky changes and keep graph-backed answers current during long work sessions.","opensInNewTab":false,"href":"/demo/project-graph/impact"}]}]},{"key":"testing","label":"Testing","summary":"Contract-level verification patterns for payloads and other framework boundaries.","icon":"QA","eyebrow":"Verification","starter":false,"prerequisites":["routing"],"featureCount":1,"href":"/demo/testing","features":[{"section":"testing","slug":"payload-contracts","label":"Testing","title":"Payload Contract Testing","summary":"Run one project-level contract suite through the canonical test runner and let strategy profiles verify payload boundaries without hand-writing repetitive negative cases.","opensInNewTab":false,"href":"/demo/testing/payload-contracts"}],"groups":[{"key":"contracts","label":"Contracts","featureCount":1,"features":[{"section":"testing","slug":"payload-contracts","label":"Testing","title":"Payload Contract Testing","summary":"Run one project-level contract suite through the canonical test runner and let strategy profiles verify payload boundaries without hand-writing repetitive negative cases.","opensInNewTab":false,"href":"/demo/testing/payload-contracts"}]}]}],"featureTree":[{"key":"start-here","label":"Start Here","eyebrow":"Guided path","summary":"The smallest reliable sequence that makes Semitexa feel like a system instead of a brochure.","type":"feature-links","href":"/#start-here","features":[{"section":"get-started","slug":"installation","label":"Start Here","title":"Installation","summary":"Create the project, review the baseline env contract, and bring up the Semitexa runtime the supported way.","opensInNewTab":false,"href":"/demo/get-started/installation"},{"section":"get-started","slug":"local-domain","label":"Start Here","title":"Local Domain","summary":"Register .test domains through the built-in local-domain helper instead of relying on ad hoc host setup.","opensInNewTab":false,"href":"/demo/get-started/local-domain"},{"section":"get-started","slug":"module-structure","label":"Start Here","title":"Module Structure","summary":"The minimal Semitexa module is a typed HTTP spine of payload, handler, resource, and template.","opensInNewTab":false,"href":"/demo/get-started/module-structure"},{"section":"get-started","slug":"base-tenant","label":"Start Here","title":"Base Tenant","summary":"Establish one default tenant context early so tenant-aware behavior is visible before the rest of the application grows.","opensInNewTab":false,"href":"/demo/get-started/base-tenant"},{"section":"get-started","slug":"locale-setup","label":"Start Here","title":"Locale Setup","summary":"Configure the minimal Locale contract so translations and locale-aware rendering become explicit early.","opensInNewTab":false,"href":"/demo/get-started/locale-setup"},{"section":"get-started","slug":"ai-console","label":"Start Here","title":"AI Console","summary":"Use the Semitexa AI console as a command translation surface over real operator commands.","opensInNewTab":false,"href":"/demo/get-started/ai-console"},{"section":"project-graph","slug":"overview","label":"Project Graph","title":"Project Graph Overview","summary":"Understand what `semitexa-project-graph` adds: a stored structural map, an intelligence layer, and task-scoped context for large-codebase work.","opensInNewTab":false,"href":"/demo/project-graph/overview"},{"section":"routing","slug":"basic","label":"Routing & Handlers","title":"Basic Route","summary":"Define a route with one access attribute on the payload — no XML, no YAML, no config files.","opensInNewTab":false,"href":"/demo/routing/basic"},{"section":"di","slug":"overview","label":"Dependency Injection","title":"DI Canon","summary":"One canonical DI path for container-managed classes: explicit properties, explicit lifecycles, deterministic boot.","opensInNewTab":false,"href":"/demo/di/overview"}],"featureCount":9},{"key":"full-catalog","label":"Full Catalog","eyebrow":"Route-first map","summary":"The exhaustive live map, still route-first, still real, and still one click away from every feature route.","type":"section-groups","href":"/#full-catalog","sectionKeys":["get-started","routing","di","data","auth","events","rendering","platform","api","cli","project-graph","llm","testing"],"featureCount":81,"sections":[{"key":"get-started","label":"Start Here","sidebarLabel":"Get Started","summary":"The shortest path from fresh scaffold to a trustworthy local Semitexa runtime: install, boot, understand the module map, bind a real host, and reach the first tenant boundary.","icon":"GO","eyebrow":"Onboarding","starter":true,"prerequisites":[],"featureCount":7,"href":"/demo/get-started","features":[{"section":"get-started","slug":"module-structure","label":"Start Here","title":"Module Structure","summary":"The minimal Semitexa module is a typed HTTP spine of payload, handler, resource, and template.","opensInNewTab":false,"href":"/demo/get-started/module-structure"},{"section":"get-started","slug":"installation","label":"Start Here","title":"Installation","summary":"Create the project, review the baseline env contract, and bring up the Semitexa runtime the supported way.","opensInNewTab":false,"href":"/demo/get-started/installation"},{"section":"get-started","slug":"local-domain","label":"Start Here","title":"Local Domain","summary":"Register .test domains through the built-in local-domain helper instead of relying on ad hoc host setup.","opensInNewTab":false,"href":"/demo/get-started/local-domain"},{"section":"get-started","slug":"base-tenant","label":"Start Here","title":"Base Tenant","summary":"Establish one default tenant context early so tenant-aware behavior is visible before the rest of the application grows.","opensInNewTab":false,"href":"/demo/get-started/base-tenant"},{"section":"get-started","slug":"locale-setup","label":"Start Here","title":"Locale Setup","summary":"Configure the minimal Locale contract so translations and locale-aware rendering become explicit early.","opensInNewTab":false,"href":"/demo/get-started/locale-setup"},{"section":"get-started","slug":"ai-console","label":"Start Here","title":"AI Console","summary":"Use the Semitexa AI console as a command translation surface over real operator commands.","opensInNewTab":false,"href":"/demo/get-started/ai-console"},{"section":"get-started","slug":"beyond-controllers","label":"Start Here","title":"Beyond Controllers","summary":"Understand why Semitexa keeps transport, use case, and rendering as separate explicit responsibilities.","opensInNewTab":false,"href":"/demo/get-started/beyond-controllers"}],"groups":[{"key":"structure","label":"Module Structure","featureCount":1,"features":[{"section":"get-started","slug":"module-structure","label":"Start Here","title":"Module Structure","summary":"The minimal Semitexa module is a typed HTTP spine of payload, handler, resource, and template.","opensInNewTab":false,"href":"/demo/get-started/module-structure"}]},{"key":"onboarding","label":"Onboarding","featureCount":6,"features":[{"section":"get-started","slug":"installation","label":"Start Here","title":"Installation","summary":"Create the project, review the baseline env contract, and bring up the Semitexa runtime the supported way.","opensInNewTab":false,"href":"/demo/get-started/installation"},{"section":"get-started","slug":"local-domain","label":"Start Here","title":"Local Domain","summary":"Register .test domains through the built-in local-domain helper instead of relying on ad hoc host setup.","opensInNewTab":false,"href":"/demo/get-started/local-domain"},{"section":"get-started","slug":"base-tenant","label":"Start Here","title":"Base Tenant","summary":"Establish one default tenant context early so tenant-aware behavior is visible before the rest of the application grows.","opensInNewTab":false,"href":"/demo/get-started/base-tenant"},{"section":"get-started","slug":"locale-setup","label":"Start Here","title":"Locale Setup","summary":"Configure the minimal Locale contract so translations and locale-aware rendering become explicit early.","opensInNewTab":false,"href":"/demo/get-started/locale-setup"},{"section":"get-started","slug":"ai-console","label":"Start Here","title":"AI Console","summary":"Use the Semitexa AI console as a command translation surface over real operator commands.","opensInNewTab":false,"href":"/demo/get-started/ai-console"},{"section":"get-started","slug":"beyond-controllers","label":"Start Here","title":"Beyond Controllers","summary":"Understand why Semitexa keeps transport, use case, and rendering as separate explicit responsibilities.","opensInNewTab":false,"href":"/demo/get-started/beyond-controllers"}]}]},{"key":"routing","label":"Routing & Handlers","summary":"Attribute-driven routes, typed handlers, and content negotiation in one coherent request pipeline.","icon":"RT","eyebrow":"HTTP Layer","starter":true,"prerequisites":[],"featureCount":7,"href":"/demo/routing","features":[{"section":"routing","slug":"basic","label":"Routing & Handlers","title":"Basic Route","summary":"Define a route with one access attribute on the payload — no XML, no YAML, no config files.","opensInNewTab":false,"href":"/demo/routing/basic"},{"section":"routing","slug":"parameterized","label":"Routing & Handlers","title":"Parameterized Route","summary":"Path parameters with regex constraints and typed injection.","opensInNewTab":false,"href":"/demo/routing/parameterized/headphones"},{"section":"routing","slug":"env-route-override","label":"Routing & Handlers","title":"Env Route Override","summary":"Keep the payload as the route source of truth while allowing operations to remap the public URL through .env.","opensInNewTab":false,"href":"/demo/routing/env-route-override"},{"section":"routing","slug":"payload-shield","label":"Routing & Handlers","title":"Payload As A Shield","summary":"Hydration happens before the handler, and each setter owns the normalization and guard logic for its own field.","opensInNewTab":false,"href":"/demo/routing/payload-shield"},{"section":"routing","slug":"payload-parts","label":"Routing & Handlers","title":"Payload Parts","summary":"One module owns the route, another module can extend the same payload contract without forking or reopening the base class.","opensInNewTab":false,"href":"/demo/routing/payload-parts"},{"section":"routing","slug":"content-negotiation","label":"Routing & Handlers","title":"Content Negotiation","summary":"One endpoint, multiple response formats — automatically.","opensInNewTab":false,"href":"/demo/routing/content-negotiation"},{"section":"routing","slug":"public-endpoint","label":"Routing & Handlers","title":"Public Payload","summary":"Anonymous endpoints opt in explicitly with the public access attribute. Every other payload requires authentication.","opensInNewTab":false,"href":"/demo/routing/public-endpoint"}],"groups":[{"key":"foundations","label":"Foundations","featureCount":3,"features":[{"section":"routing","slug":"basic","label":"Routing & Handlers","title":"Basic Route","summary":"Define a route with one access attribute on the payload — no XML, no YAML, no config files.","opensInNewTab":false,"href":"/demo/routing/basic"},{"section":"routing","slug":"parameterized","label":"Routing & Handlers","title":"Parameterized Route","summary":"Path parameters with regex constraints and typed injection.","opensInNewTab":false,"href":"/demo/routing/parameterized/headphones"},{"section":"routing","slug":"env-route-override","label":"Routing & Handlers","title":"Env Route Override","summary":"Keep the payload as the route source of truth while allowing operations to remap the public URL through .env.","opensInNewTab":false,"href":"/demo/routing/env-route-override"}]},{"key":"request-model","label":"Request Model","featureCount":2,"features":[{"section":"routing","slug":"payload-shield","label":"Routing & Handlers","title":"Payload As A Shield","summary":"Hydration happens before the handler, and each setter owns the normalization and guard logic for its own field.","opensInNewTab":false,"href":"/demo/routing/payload-shield"},{"section":"routing","slug":"payload-parts","label":"Routing & Handlers","title":"Payload Parts","summary":"One module owns the route, another module can extend the same payload contract without forking or reopening the base class.","opensInNewTab":false,"href":"/demo/routing/payload-parts"}]},{"key":"delivery","label":"Delivery","featureCount":2,"features":[{"section":"routing","slug":"content-negotiation","label":"Routing & Handlers","title":"Content Negotiation","summary":"One endpoint, multiple response formats — automatically.","opensInNewTab":false,"href":"/demo/routing/content-negotiation"},{"section":"routing","slug":"public-endpoint","label":"Routing & Handlers","title":"Public Payload","summary":"Anonymous endpoints opt in explicitly with the public access attribute. Every other payload requires authentication.","opensInNewTab":false,"href":"/demo/routing/public-endpoint"}]}]},{"key":"di","label":"Dependency Injection","summary":"Single-path DI with explicit lifecycles, deterministic contracts, and stable boot behavior for long-running workers.","icon":"DI","eyebrow":"Container","starter":true,"prerequisites":[],"featureCount":5,"href":"/demo/di","features":[{"section":"di","slug":"overview","label":"Dependency Injection","title":"DI Canon","summary":"One canonical DI path for container-managed classes: explicit properties, explicit lifecycles, deterministic boot.","opensInNewTab":false,"href":"/demo/di/overview"},{"section":"di","slug":"readonly","label":"Dependency Injection","title":"Readonly Injection","summary":"One explicit DI path, one shared worker instance — fast at runtime and stable under reload.","opensInNewTab":false,"href":"/demo/di/readonly"},{"section":"di","slug":"mutable","label":"Dependency Injection","title":"Mutable Injection","summary":"Execution-scoped services get a fresh clone every run — safe state without contaminating the worker.","opensInNewTab":false,"href":"/demo/di/mutable"},{"section":"di","slug":"factory","label":"Dependency Injection","title":"Factory Injection","summary":"On-demand creation stays explicit — lazy instances without falling back to service locator habits.","opensInNewTab":false,"href":"/demo/di/factory"},{"section":"di","slug":"contracts","label":"Dependency Injection","title":"Service Contracts","summary":"Depend on contracts, but keep ownership explicit — deterministic substitution instead of runtime magic.","opensInNewTab":false,"href":"/demo/di/contracts"}],"groups":[{"key":"container-basics","label":"Container Basics","featureCount":5,"features":[{"section":"di","slug":"overview","label":"Dependency Injection","title":"DI Canon","summary":"One canonical DI path for container-managed classes: explicit properties, explicit lifecycles, deterministic boot.","opensInNewTab":false,"href":"/demo/di/overview"},{"section":"di","slug":"readonly","label":"Dependency Injection","title":"Readonly Injection","summary":"One explicit DI path, one shared worker instance — fast at runtime and stable under reload.","opensInNewTab":false,"href":"/demo/di/readonly"},{"section":"di","slug":"mutable","label":"Dependency Injection","title":"Mutable Injection","summary":"Execution-scoped services get a fresh clone every run — safe state without contaminating the worker.","opensInNewTab":false,"href":"/demo/di/mutable"},{"section":"di","slug":"factory","label":"Dependency Injection","title":"Factory Injection","summary":"On-demand creation stays explicit — lazy instances without falling back to service locator habits.","opensInNewTab":false,"href":"/demo/di/factory"},{"section":"di","slug":"contracts","label":"Dependency Injection","title":"Service Contracts","summary":"Depend on contracts, but keep ownership explicit — deterministic substitution instead of runtime magic.","opensInNewTab":false,"href":"/demo/di/contracts"}]}]},{"key":"data","label":"Persistence","summary":"Attribute-mapped resources, repositories, filtering, pagination, and relations with real demo data.","icon":"DB","eyebrow":"Persistence","starter":true,"prerequisites":[],"featureCount":9,"href":"/demo/data","features":[{"section":"data","slug":"domain-models","label":"Persistence","title":"Domain-Level Models","summary":"Semitexa separates persistence resources from business models. Resources map tables; domain models carry behavior and invariants.","opensInNewTab":false,"href":"/demo/data/domain-models"},{"section":"data","slug":"repository-workflow","label":"Persistence","title":"Repository Workflow","summary":"The canonical Semitexa path: handlers depend on repository contracts, repositories return domain models, and persistence resources stay behind the boundary.","opensInNewTab":false,"href":"/demo/data/repository-workflow"},{"section":"data","slug":"schema-sync","label":"Persistence","title":"Schema Sync, Not Migration Churn","summary":"Semitexa creates SQL only when the real schema changed, blocks destructive drops by default, and logs the exact DDL plan as SQL and JSON.","opensInNewTab":false,"href":"/demo/data/schema-sync"},{"section":"data","slug":"query","label":"Persistence","title":"Query Builder","summary":"Compose type-safe queries with a fluent API — no raw SQL, no magic strings.","opensInNewTab":false,"href":"/demo/data/query"},{"section":"data","slug":"filtering","label":"Persistence","title":"Filtering","summary":"Mark a property #[Filterable] and the ORM handles the rest — no manual WHERE clauses.","opensInNewTab":false,"href":"/demo/data/filtering"},{"section":"data","slug":"pagination","label":"Persistence","title":"Pagination","summary":"Offset and cursor pagination out of the box — switch modes with a single query parameter.","opensInNewTab":false,"href":"/demo/data/pagination"},{"section":"data","slug":"relations","label":"Persistence","title":"Relations","summary":"Declare parent and child links on the resource itself, then read typed relations from the handler.","opensInNewTab":false,"href":"/demo/data/relations"},{"section":"data","slug":"table-extension","label":"Persistence","title":"Shared Table Extension","summary":"Two modules can extend one table independently, and the ORM merges the schema without forcing either side to edit the other.","opensInNewTab":false,"href":"/demo/data/table-extension"},{"section":"data","slug":"n-plus-one","label":"Persistence","title":"N+1 Without Magic","summary":"Semitexa avoids N+1 by using resource slices for the exact columns and relations each screen needs, instead of hiding database traffic behind implicit relation loading.","opensInNewTab":false,"href":"/demo/data/n-plus-one"}],"groups":[{"key":"modeling","label":"Modeling & Workflow","featureCount":3,"features":[{"section":"data","slug":"domain-models","label":"Persistence","title":"Domain-Level Models","summary":"Semitexa separates persistence resources from business models. Resources map tables; domain models carry behavior and invariants.","opensInNewTab":false,"href":"/demo/data/domain-models"},{"section":"data","slug":"repository-workflow","label":"Persistence","title":"Repository Workflow","summary":"The canonical Semitexa path: handlers depend on repository contracts, repositories return domain models, and persistence resources stay behind the boundary.","opensInNewTab":false,"href":"/demo/data/repository-workflow"},{"section":"data","slug":"schema-sync","label":"Persistence","title":"Schema Sync, Not Migration Churn","summary":"Semitexa creates SQL only when the real schema changed, blocks destructive drops by default, and logs the exact DDL plan as SQL and JSON.","opensInNewTab":false,"href":"/demo/data/schema-sync"}]},{"key":"querying","label":"Querying","featureCount":6,"features":[{"section":"data","slug":"query","label":"Persistence","title":"Query Builder","summary":"Compose type-safe queries with a fluent API — no raw SQL, no magic strings.","opensInNewTab":false,"href":"/demo/data/query"},{"section":"data","slug":"filtering","label":"Persistence","title":"Filtering","summary":"Mark a property #[Filterable] and the ORM handles the rest — no manual WHERE clauses.","opensInNewTab":false,"href":"/demo/data/filtering"},{"section":"data","slug":"pagination","label":"Persistence","title":"Pagination","summary":"Offset and cursor pagination out of the box — switch modes with a single query parameter.","opensInNewTab":false,"href":"/demo/data/pagination"},{"section":"data","slug":"relations","label":"Persistence","title":"Relations","summary":"Declare parent and child links on the resource itself, then read typed relations from the handler.","opensInNewTab":false,"href":"/demo/data/relations"},{"section":"data","slug":"table-extension","label":"Persistence","title":"Shared Table Extension","summary":"Two modules can extend one table independently, and the ORM merges the schema without forcing either side to edit the other.","opensInNewTab":false,"href":"/demo/data/table-extension"},{"section":"data","slug":"n-plus-one","label":"Persistence","title":"N+1 Without Magic","summary":"Semitexa avoids N+1 by using resource slices for the exact columns and relations each screen needs, instead of hiding database traffic behind implicit relation loading.","opensInNewTab":false,"href":"/demo/data/n-plus-one"}]}]},{"key":"auth","label":"Security","summary":"Typed session payloads, machine credentials, RBAC, and route protection without string-key auth chaos.","icon":"AU","eyebrow":"Security","starter":false,"prerequisites":[],"featureCount":7,"href":"/demo/auth","features":[{"section":"auth","slug":"session","label":"Security","title":"Session Auth","summary":"Google signs the user in, then the session stores the selected demo role and re-hydrates it on every request.","opensInNewTab":false,"href":"/demo/auth/session"},{"section":"auth","slug":"session-payloads","label":"Security","title":"Session Payloads","summary":"Semitexa forbids string-key session chaos: session state lives in typed Session Payloads or it does not exist.","opensInNewTab":false,"href":"/demo/auth/session-payloads"},{"section":"auth","slug":"google","label":"Security","title":"Google Authorization","summary":"Authorization is required for demo SSE blocks that keep a long-lived backend connection open.","opensInNewTab":false,"href":"/demo/auth/google"},{"section":"auth","slug":"machine","label":"Security","title":"Machine Auth","summary":"Service-to-service authentication via Bearer tokens — scoped, revocable, and audited.","opensInNewTab":false,"href":"/demo/auth/machine"},{"section":"auth","slug":"protected","label":"Security","title":"Protected Route","summary":"Add one access attribute and one optional permission attribute and the framework enforces access — 401 for unauthenticated requests, 403 for unauthorized ones.","opensInNewTab":false,"href":"/demo/auth/protected"},{"section":"auth","slug":"requires-permission","label":"Security","title":"Requires Permission","summary":"Declare one permission slug on the payload and let the framework enforce it before your handler runs.","opensInNewTab":false,"href":"/demo/auth/requires-permission"},{"section":"auth","slug":"rbac","label":"Security","title":"RBAC","summary":"Hybrid RBAC with coarse-grained capabilities, exact permission slugs, and module-owned permission catalogs.","opensInNewTab":false,"href":"/demo/auth/rbac"}],"groups":[{"key":"identity","label":"Identity","featureCount":4,"features":[{"section":"auth","slug":"session","label":"Security","title":"Session Auth","summary":"Google signs the user in, then the session stores the selected demo role and re-hydrates it on every request.","opensInNewTab":false,"href":"/demo/auth/session"},{"section":"auth","slug":"session-payloads","label":"Security","title":"Session Payloads","summary":"Semitexa forbids string-key session chaos: session state lives in typed Session Payloads or it does not exist.","opensInNewTab":false,"href":"/demo/auth/session-payloads"},{"section":"auth","slug":"google","label":"Security","title":"Google Authorization","summary":"Authorization is required for demo SSE blocks that keep a long-lived backend connection open.","opensInNewTab":false,"href":"/demo/auth/google"},{"section":"auth","slug":"machine","label":"Security","title":"Machine Auth","summary":"Service-to-service authentication via Bearer tokens — scoped, revocable, and audited.","opensInNewTab":false,"href":"/demo/auth/machine"}]},{"key":"access-control","label":"Access Control","featureCount":3,"features":[{"section":"auth","slug":"protected","label":"Security","title":"Protected Route","summary":"Add one access attribute and one optional permission attribute and the framework enforces access — 401 for unauthenticated requests, 403 for unauthorized ones.","opensInNewTab":false,"href":"/demo/auth/protected"},{"section":"auth","slug":"requires-permission","label":"Security","title":"Requires Permission","summary":"Declare one permission slug on the payload and let the framework enforce it before your handler runs.","opensInNewTab":false,"href":"/demo/auth/requires-permission"},{"section":"auth","slug":"rbac","label":"Security","title":"RBAC","summary":"Hybrid RBAC with coarse-grained capabilities, exact permission slugs, and module-owned permission catalogs.","opensInNewTab":false,"href":"/demo/auth/rbac"}]}]},{"key":"events","label":"Async","summary":"Synchronous and deferred event flows, queues, and SSE-style interactions.","icon":"EV","eyebrow":"Async","starter":false,"prerequisites":[],"featureCount":5,"href":"/demo/events","features":[{"section":"events","slug":"arena","label":"Async","title":"Execution Arena","summary":"Launch the same backend intent in sync, Swoole async, and queued modes, then watch the proof arrive over SSE.","opensInNewTab":false,"href":"/demo/events/arena"},{"section":"events","slug":"sync","label":"Async","title":"Sync Events","summary":"Dispatch an event and all sync listeners run before the response is sent.","opensInNewTab":false,"href":"/demo/events/sync"},{"section":"events","slug":"deferred","label":"Async","title":"Deferred Handler","summary":"Heavy work runs after the response is sent — the user gets instant feedback.","opensInNewTab":false,"href":"/demo/events/deferred"},{"section":"events","slug":"queued","label":"Async","title":"Queued Handler","summary":"Events survive restarts and scale across workers — backed by a durable message queue.","opensInNewTab":false,"href":"/demo/events/queued"},{"section":"events","slug":"sse","label":"Async","title":"SSE Stream","summary":"Real-time server push without WebSockets — connect once and receive real backend events over plain HTTP.","opensInNewTab":false,"href":"/demo/events/sse"}],"groups":[{"key":"event-flow","label":"Event Flow","featureCount":5,"features":[{"section":"events","slug":"arena","label":"Async","title":"Execution Arena","summary":"Launch the same backend intent in sync, Swoole async, and queued modes, then watch the proof arrive over SSE.","opensInNewTab":false,"href":"/demo/events/arena"},{"section":"events","slug":"sync","label":"Async","title":"Sync Events","summary":"Dispatch an event and all sync listeners run before the response is sent.","opensInNewTab":false,"href":"/demo/events/sync"},{"section":"events","slug":"deferred","label":"Async","title":"Deferred Handler","summary":"Heavy work runs after the response is sent — the user gets instant feedback.","opensInNewTab":false,"href":"/demo/events/deferred"},{"section":"events","slug":"queued","label":"Async","title":"Queued Handler","summary":"Events survive restarts and scale across workers — backed by a durable message queue.","opensInNewTab":false,"href":"/demo/events/queued"},{"section":"events","slug":"sse","label":"Async","title":"SSE Stream","summary":"Real-time server push without WebSockets — connect once and receive real backend events over plain HTTP.","opensInNewTab":false,"href":"/demo/events/sse"}]}]},{"key":"rendering","label":"UI Rendering & SSR","summary":"One rendering story from handler to HTML: page data, page regions, and live updates stay in the same server-driven model instead of splitting into frontend and backend template logic.","icon":"UI","eyebrow":"Frontend","starter":false,"prerequisites":[],"featureCount":15,"href":"/demo/rendering","features":[{"section":"rendering","slug":"philosophy","label":"UI Rendering & SSR","title":"SSR Philosophy","summary":"Semitexa SSR is one continuous rendering architecture: page, slots, deferred regions, live refresh, and interactive components stay inside one server-owned story.","opensInNewTab":false,"href":"/demo/rendering/philosophy"},{"section":"rendering","slug":"resource-dtos","label":"UI Rendering & SSR","title":"Resource DTOs","summary":"A Resource DTO is the one typed source of presentation data: handlers shape it once, templates consume it everywhere, and no view has to dissect random arrays.","opensInNewTab":false,"href":"/demo/rendering/resource-dtos"},{"section":"rendering","slug":"slots","label":"UI Rendering & SSR","title":"Slot Resources","summary":"Each page region is its own resource pipeline with the same template system as the main page — no scattered partial glue, no mystery wiring.","opensInNewTab":false,"href":"/demo/rendering/slots"},{"section":"rendering","slug":"components","label":"UI Rendering & SSR","title":"Components","summary":"Reusable, attribute-registered UI components — discovered automatically from the classmap.","opensInNewTab":false,"href":"/demo/rendering/components"},{"section":"rendering","slug":"seo","label":"UI Rendering & SSR","title":"SEO","summary":"Set title, description, and Open Graph tags from your handler — no template hacks needed.","opensInNewTab":false,"href":"/demo/rendering/seo"},{"section":"rendering","slug":"assets","label":"UI Rendering & SSR","title":"Asset Pipeline","summary":"Declare assets with glob patterns in assets.json — served, versioned, and injected automatically.","opensInNewTab":false,"href":"/demo/rendering/assets"},{"section":"rendering","slug":"component-scripts","label":"UI Rendering & SSR","title":"Component Script Assets","summary":"A Semitexa SSR component can own its optional enhancement asset, so behavior travels with the component instead of leaking into page-level glue.","opensInNewTab":false,"href":"/demo/rendering/component-scripts"},{"section":"rendering","slug":"deferred-scripts","label":"UI Rendering & SSR","title":"Script Injection","summary":"Deferred blocks carry their own JS — injected once when the block arrives, never duplicated.","opensInNewTab":false,"href":"/demo/rendering/deferred-scripts"},{"section":"rendering","slug":"deferred","label":"UI Rendering & SSR","title":"Deferred Blocks","summary":"SSR renders the shell first, then expensive regions stream in as real HTML over SSE — no SPA handoff and no client-side page rebuild.","opensInNewTab":true,"href":"/demo/rendering/deferred"},{"section":"rendering","slug":"deferred-encapsulation","label":"UI Rendering & SSR","title":"Block Isolation","summary":"Two identical blocks on the same page run independently — scoped DOM, scoped JS, no conflicts.","opensInNewTab":false,"href":"/demo/rendering/deferred-encapsulation"},{"section":"rendering","slug":"deferred-live","label":"UI Rendering & SSR","title":"Live Widgets","summary":"A live slot can refresh itself on a timer while the page stays SSR-first — no SPA runtime and no handwritten polling layer.","opensInNewTab":false,"href":"/demo/rendering/deferred-live"},{"section":"rendering","slug":"reactive-report","label":"UI Rendering & SSR","title":"Reactive Report","summary":"Background work updates an SSR-first slot in place, so the UI feels live without falling back to SPA state orchestration.","opensInNewTab":false,"href":"/demo/rendering/reactive-report"},{"section":"rendering","slug":"reactive-import","label":"UI Rendering & SSR","title":"Reactive Import","summary":"Background batches keep moving, and the page reflects server progress as live HTML instead of a client-managed progress app.","opensInNewTab":false,"href":"/demo/rendering/reactive-import"},{"section":"rendering","slug":"reactive-analytics","label":"UI Rendering & SSR","title":"Reactive Analytics","summary":"Independent analytics jobs can light up one dashboard progressively, while the page stays server-rendered from the first byte.","opensInNewTab":false,"href":"/demo/rendering/reactive-analytics"},{"section":"rendering","slug":"reactive-ai","label":"UI Rendering & SSR","title":"Reactive AI Task","summary":"Submit a task and watch the AI pipeline stages reveal one by one as the cron job processes it.","opensInNewTab":false,"href":"/demo/rendering/reactive-ai"}],"groups":[{"key":"rendering-model","label":"SSR Foundation","featureCount":8,"features":[{"section":"rendering","slug":"philosophy","label":"UI Rendering & SSR","title":"SSR Philosophy","summary":"Semitexa SSR is one continuous rendering architecture: page, slots, deferred regions, live refresh, and interactive components stay inside one server-owned story.","opensInNewTab":false,"href":"/demo/rendering/philosophy"},{"section":"rendering","slug":"resource-dtos","label":"UI Rendering & SSR","title":"Resource DTOs","summary":"A Resource DTO is the one typed source of presentation data: handlers shape it once, templates consume it everywhere, and no view has to dissect random arrays.","opensInNewTab":false,"href":"/demo/rendering/resource-dtos"},{"section":"rendering","slug":"slots","label":"UI Rendering & SSR","title":"Slot Resources","summary":"Each page region is its own resource pipeline with the same template system as the main page — no scattered partial glue, no mystery wiring.","opensInNewTab":false,"href":"/demo/rendering/slots"},{"section":"rendering","slug":"components","label":"UI Rendering & SSR","title":"Components","summary":"Reusable, attribute-registered UI components — discovered automatically from the classmap.","opensInNewTab":false,"href":"/demo/rendering/components"},{"section":"rendering","slug":"seo","label":"UI Rendering & SSR","title":"SEO","summary":"Set title, description, and Open Graph tags from your handler — no template hacks needed.","opensInNewTab":false,"href":"/demo/rendering/seo"},{"section":"rendering","slug":"assets","label":"UI Rendering & SSR","title":"Asset Pipeline","summary":"Declare assets with glob patterns in assets.json — served, versioned, and injected automatically.","opensInNewTab":false,"href":"/demo/rendering/assets"},{"section":"rendering","slug":"component-scripts","label":"UI Rendering & SSR","title":"Component Script Assets","summary":"A Semitexa SSR component can own its optional enhancement asset, so behavior travels with the component instead of leaking into page-level glue.","opensInNewTab":false,"href":"/demo/rendering/component-scripts"},{"section":"rendering","slug":"deferred-scripts","label":"UI Rendering & SSR","title":"Script Injection","summary":"Deferred blocks carry their own JS — injected once when the block arrives, never duplicated.","opensInNewTab":false,"href":"/demo/rendering/deferred-scripts"}]},{"key":"deferred","label":"Deferred Delivery","featureCount":2,"features":[{"section":"rendering","slug":"deferred","label":"UI Rendering & SSR","title":"Deferred Blocks","summary":"SSR renders the shell first, then expensive regions stream in as real HTML over SSE — no SPA handoff and no client-side page rebuild.","opensInNewTab":true,"href":"/demo/rendering/deferred"},{"section":"rendering","slug":"deferred-encapsulation","label":"UI Rendering & SSR","title":"Block Isolation","summary":"Two identical blocks on the same page run independently — scoped DOM, scoped JS, no conflicts.","opensInNewTab":false,"href":"/demo/rendering/deferred-encapsulation"}]},{"key":"live","label":"Reactive UI","featureCount":5,"features":[{"section":"rendering","slug":"deferred-live","label":"UI Rendering & SSR","title":"Live Widgets","summary":"A live slot can refresh itself on a timer while the page stays SSR-first — no SPA runtime and no handwritten polling layer.","opensInNewTab":false,"href":"/demo/rendering/deferred-live"},{"section":"rendering","slug":"reactive-report","label":"UI Rendering & SSR","title":"Reactive Report","summary":"Background work updates an SSR-first slot in place, so the UI feels live without falling back to SPA state orchestration.","opensInNewTab":false,"href":"/demo/rendering/reactive-report"},{"section":"rendering","slug":"reactive-import","label":"UI Rendering & SSR","title":"Reactive Import","summary":"Background batches keep moving, and the page reflects server progress as live HTML instead of a client-managed progress app.","opensInNewTab":false,"href":"/demo/rendering/reactive-import"},{"section":"rendering","slug":"reactive-analytics","label":"UI Rendering & SSR","title":"Reactive Analytics","summary":"Independent analytics jobs can light up one dashboard progressively, while the page stays server-rendered from the first byte.","opensInNewTab":false,"href":"/demo/rendering/reactive-analytics"},{"section":"rendering","slug":"reactive-ai","label":"UI Rendering & SSR","title":"Reactive AI Task","summary":"Submit a task and watch the AI pipeline stages reveal one by one as the cron job processes it.","opensInNewTab":false,"href":"/demo/rendering/reactive-ai"}]}]},{"key":"platform","label":"Tenancy","summary":"Multi-tenant resolution, tenant-aware configuration, and strict isolation of data and background work.","icon":"TN","eyebrow":"Multi-Tenant","starter":false,"prerequisites":["data","rendering"],"featureCount":5,"href":"/demo/platform","features":[{"section":"platform","slug":"tenancy-resolution","label":"Tenancy","title":"Tenant Context Resolution","summary":"See how Semitexa resolves the active tenant from subdomain, header, path, or query input before the rest of the platform runs.","opensInNewTab":false,"href":"/demo/platform/tenancy-resolution"},{"section":"platform","slug":"tenancy-config","label":"Tenancy","title":"Per-Tenant Configuration","summary":"Three demo tenants with distinct branding -- switch tenant, everything changes without if/else.","opensInNewTab":false,"href":"/demo/platform/tenancy-config"},{"section":"platform","slug":"tenancy-layers","label":"Tenancy","title":"Multi-Layer Tenancy","summary":"Organization, Locale, Theme, Environment -- four independent layers compose into one TenantContext.","opensInNewTab":false,"href":"/demo/platform/tenancy-layers"},{"section":"platform","slug":"tenancy-isolation","label":"Tenancy","title":"Data Isolation","summary":"Product listing scoped by tenant -- switch tenant, list changes. Zero manual WHERE clauses.","opensInNewTab":false,"href":"/demo/platform/tenancy-isolation"},{"section":"platform","slug":"tenancy-queue","label":"Tenancy","title":"Queue Tenant Propagation","summary":"Tenant context travels with queued jobs -- _tenant key injected automatically, restored by worker.","opensInNewTab":false,"href":"/demo/platform/tenancy-queue"}],"groups":[{"key":"resolution","label":"Tenant Resolution","featureCount":1,"features":[{"section":"platform","slug":"tenancy-resolution","label":"Tenancy","title":"Tenant Context Resolution","summary":"See how Semitexa resolves the active tenant from subdomain, header, path, or query input before the rest of the platform runs.","opensInNewTab":false,"href":"/demo/platform/tenancy-resolution"}]},{"key":"configuration","label":"Tenant Configuration","featureCount":2,"features":[{"section":"platform","slug":"tenancy-config","label":"Tenancy","title":"Per-Tenant Configuration","summary":"Three demo tenants with distinct branding -- switch tenant, everything changes without if/else.","opensInNewTab":false,"href":"/demo/platform/tenancy-config"},{"section":"platform","slug":"tenancy-layers","label":"Tenancy","title":"Multi-Layer Tenancy","summary":"Organization, Locale, Theme, Environment -- four independent layers compose into one TenantContext.","opensInNewTab":false,"href":"/demo/platform/tenancy-layers"}]},{"key":"isolation","label":"Isolation & Work","featureCount":2,"features":[{"section":"platform","slug":"tenancy-isolation","label":"Tenancy","title":"Data Isolation","summary":"Product listing scoped by tenant -- switch tenant, list changes. Zero manual WHERE clauses.","opensInNewTab":false,"href":"/demo/platform/tenancy-isolation"},{"section":"platform","slug":"tenancy-queue","label":"Tenancy","title":"Queue Tenant Propagation","summary":"Tenant context travels with queued jobs -- _tenant key injected automatically, restored by worker.","opensInNewTab":false,"href":"/demo/platform/tenancy-queue"}]}]},{"key":"api","label":"API","summary":"External API endpoints, machine auth, versioning, and consumer-facing schema behavior.","icon":"API","eyebrow":"Machine","starter":false,"prerequisites":["routing","auth"],"featureCount":7,"href":"/demo/api","features":[{"section":"api","slug":"rest-api","label":"API","title":"REST API","summary":"Classic Semitexa REST endpoints with typed payloads, versioning, and consumer-friendly response shaping.","opensInNewTab":false,"href":"/demo/api/rest-api"},{"section":"api","slug":"structured-errors","label":"API","title":"Structured Errors","summary":"Throw domain exceptions and let semitexa-api map them into stable machine-readable error envelopes.","opensInNewTab":false,"href":"/demo/api/structured-errors"},{"section":"api","slug":"active-version","label":"API","title":"Active Version","summary":"The current collection endpoint with a clean X-Api-Version header and no deprecation noise.","opensInNewTab":false,"href":"/demo/api/active-version"},{"section":"api","slug":"sunset-version","label":"API","title":"Sunset Version","summary":"A deprecated product endpoint that emits both Deprecation and Sunset headers.","opensInNewTab":false,"href":"/demo/api/sunset-version"},{"section":"api","slug":"schema-discovery","label":"API","title":"Schema Discovery","summary":"A mini Swagger-style explorer for the live product API contract, schema endpoint, and response shapes.","opensInNewTab":false,"href":"/demo/api/schema-discovery"},{"section":"api","slug":"graphql","label":"API","title":"GraphQL API","summary":"GraphQL-first Semitexa contracts built with typed payloads and typed output DTOs instead of resolver sprawl.","opensInNewTab":false,"href":"/demo/api/graphql"},{"section":"api","slug":"rest-graphql","label":"API","title":"REST + GraphQL","summary":"One Semitexa use case can serve both REST and GraphQL without duplicating handler logic into separate resolver classes.","opensInNewTab":false,"href":"/demo/api/rest-graphql"}],"groups":[{"key":"public-api","label":"REST Surface","featureCount":4,"features":[{"section":"api","slug":"rest-api","label":"API","title":"REST API","summary":"Classic Semitexa REST endpoints with typed payloads, versioning, and consumer-friendly response shaping.","opensInNewTab":false,"href":"/demo/api/rest-api"},{"section":"api","slug":"structured-errors","label":"API","title":"Structured Errors","summary":"Throw domain exceptions and let semitexa-api map them into stable machine-readable error envelopes.","opensInNewTab":false,"href":"/demo/api/structured-errors"},{"section":"api","slug":"active-version","label":"API","title":"Active Version","summary":"The current collection endpoint with a clean X-Api-Version header and no deprecation noise.","opensInNewTab":false,"href":"/demo/api/active-version"},{"section":"api","slug":"sunset-version","label":"API","title":"Sunset Version","summary":"A deprecated product endpoint that emits both Deprecation and Sunset headers.","opensInNewTab":false,"href":"/demo/api/sunset-version"}]},{"key":"schema","label":"Schema Discovery","featureCount":3,"features":[{"section":"api","slug":"schema-discovery","label":"API","title":"Schema Discovery","summary":"A mini Swagger-style explorer for the live product API contract, schema endpoint, and response shapes.","opensInNewTab":false,"href":"/demo/api/schema-discovery"},{"section":"api","slug":"graphql","label":"API","title":"GraphQL API","summary":"GraphQL-first Semitexa contracts built with typed payloads and typed output DTOs instead of resolver sprawl.","opensInNewTab":false,"href":"/demo/api/graphql"},{"section":"api","slug":"rest-graphql","label":"API","title":"REST + GraphQL","summary":"One Semitexa use case can serve both REST and GraphQL without duplicating handler logic into separate resolver classes.","opensInNewTab":false,"href":"/demo/api/rest-graphql"}]}]},{"key":"cli","label":"CLI","summary":"Operational, introspection, and AI-oriented command surfaces that explain and drive the framework from the terminal.","icon":"CLI","eyebrow":"Operations","starter":false,"prerequisites":["routing"],"featureCount":6,"href":"/demo/cli","features":[{"section":"cli","slug":"describe-commands","label":"CLI","title":"Project Graph Introspection","summary":"Routes, modules, contracts, and handlers can be introspected directly from the CLI instead of reverse-engineering the framework graph by hand.","opensInNewTab":false,"href":"/demo/cli/describe-commands"},{"section":"cli","slug":"runtime-maintenance","label":"CLI","title":"Runtime Maintenance","summary":"Reload workers, clear stale cache, sync registries, lint architecture rules, and probe handler wiring without reaching for ad-hoc shell scripts.","opensInNewTab":false,"href":"/demo/cli/runtime-maintenance"},{"section":"cli","slug":"scaffolding-generators","label":"CLI","title":"Scaffolding Generators","summary":"Scaffold modules, pages, payloads, services, and contracts through commands that already understand Semitexa structure and AI-friendly output modes.","opensInNewTab":false,"href":"/demo/cli/scaffolding-generators"},{"section":"cli","slug":"workers-scheduling","label":"CLI","title":"Workers & Scheduling","summary":"Run queues, scheduler pools, mail delivery, webhooks, and tenant-scoped commands from a coherent operator surface instead of bespoke daemons.","opensInNewTab":false,"href":"/demo/cli/workers-scheduling"},{"section":"cli","slug":"ai-tooling","label":"CLI","title":"AI Tooling Surface","summary":"Semitexa exposes AI-facing commands as explicit CLI contracts: capabilities, skills, log access, and a local assistant entrypoint.","opensInNewTab":false,"href":"/demo/cli/ai-tooling"},{"section":"cli","slug":"orm-console","label":"CLI","title":"ORM Console Toolkit","summary":"The ORM ships with a practical CLI surface: status, diff, sync, and seed commands with dry-run safety and SQL plan export.","opensInNewTab":false,"href":"/demo/cli/orm-console"}],"groups":[{"key":"inspection","label":"Describe & Inspect","featureCount":2,"features":[{"section":"cli","slug":"describe-commands","label":"CLI","title":"Project Graph Introspection","summary":"Routes, modules, contracts, and handlers can be introspected directly from the CLI instead of reverse-engineering the framework graph by hand.","opensInNewTab":false,"href":"/demo/cli/describe-commands"},{"section":"cli","slug":"runtime-maintenance","label":"CLI","title":"Runtime Maintenance","summary":"Reload workers, clear stale cache, sync registries, lint architecture rules, and probe handler wiring without reaching for ad-hoc shell scripts.","opensInNewTab":false,"href":"/demo/cli/runtime-maintenance"}]},{"key":"automation","label":"Automation","featureCount":4,"features":[{"section":"cli","slug":"scaffolding-generators","label":"CLI","title":"Scaffolding Generators","summary":"Scaffold modules, pages, payloads, services, and contracts through commands that already understand Semitexa structure and AI-friendly output modes.","opensInNewTab":false,"href":"/demo/cli/scaffolding-generators"},{"section":"cli","slug":"workers-scheduling","label":"CLI","title":"Workers & Scheduling","summary":"Run queues, scheduler pools, mail delivery, webhooks, and tenant-scoped commands from a coherent operator surface instead of bespoke daemons.","opensInNewTab":false,"href":"/demo/cli/workers-scheduling"},{"section":"cli","slug":"ai-tooling","label":"CLI","title":"AI Tooling Surface","summary":"Semitexa exposes AI-facing commands as explicit CLI contracts: capabilities, skills, log access, and a local assistant entrypoint.","opensInNewTab":false,"href":"/demo/cli/ai-tooling"},{"section":"cli","slug":"orm-console","label":"CLI","title":"ORM Console Toolkit","summary":"The ORM ships with a practical CLI surface: status, diff, sync, and seed commands with dry-run safety and SQL plan export.","opensInNewTab":false,"href":"/demo/cli/orm-console"}]}]},{"key":"project-graph","label":"Project Graph","summary":"The `semitexa-project-graph` package: stored structural graph, intelligence layer, impact analysis, and task-scoped context for serious repository work.","icon":"PG","eyebrow":"AI Accelerator","starter":true,"prerequisites":["cli"],"featureCount":3,"href":"/demo/project-graph","features":[{"section":"project-graph","slug":"overview","label":"Project Graph","title":"Project Graph Overview","summary":"Understand what `semitexa-project-graph` adds: a stored structural map, an intelligence layer, and task-scoped context for large-codebase work.","opensInNewTab":false,"href":"/demo/project-graph/overview"},{"section":"project-graph","slug":"inspection","label":"Project Graph","title":"Inspecting the Graph","summary":"Use Project Graph queries and intelligence views to inspect modules, dependencies, flows, events, and hotspots without reconstructing the repository manually.","opensInNewTab":false,"href":"/demo/project-graph/inspection"},{"section":"project-graph","slug":"impact","label":"Project Graph","title":"Impact, Context, and Watch Mode","summary":"Use impact analysis, context packing, and watch mode to scope risky changes and keep graph-backed answers current during long work sessions.","opensInNewTab":false,"href":"/demo/project-graph/impact"}],"groups":[{"key":"launch","label":"Start Here","featureCount":1,"features":[{"section":"project-graph","slug":"overview","label":"Project Graph","title":"Project Graph Overview","summary":"Understand what `semitexa-project-graph` adds: a stored structural map, an intelligence layer, and task-scoped context for large-codebase work.","opensInNewTab":false,"href":"/demo/project-graph/overview"}]},{"key":"exploration","label":"Explore & Inspect","featureCount":1,"features":[{"section":"project-graph","slug":"inspection","label":"Project Graph","title":"Inspecting the Graph","summary":"Use Project Graph queries and intelligence views to inspect modules, dependencies, flows, events, and hotspots without reconstructing the repository manually.","opensInNewTab":false,"href":"/demo/project-graph/inspection"}]},{"key":"change-safety","label":"Impact & Context","featureCount":1,"features":[{"section":"project-graph","slug":"impact","label":"Project Graph","title":"Impact, Context, and Watch Mode","summary":"Use impact analysis, context packing, and watch mode to scope risky changes and keep graph-backed answers current during long work sessions.","opensInNewTab":false,"href":"/demo/project-graph/impact"}]}]},{"key":"llm","label":"LLM Module","sidebarLabel":"LLM","summary":"The dedicated `semitexa/llm` module: AI assistant entrypoint, skill discovery, planner, executor, provider backends, and skill authoring rules.","icon":"AI","eyebrow":"semitexa/llm","starter":false,"prerequisites":["cli"],"featureCount":4,"href":"/demo/llm","features":[{"section":"llm","slug":"overview","label":"LLM Module","title":"LLM Module Overview","summary":"What `semitexa/llm` adds to the framework and how your project can expose its own CLI skills to the assistant.","opensInNewTab":false,"href":"/demo/llm/overview"},{"section":"llm","slug":"providers","label":"LLM Module","title":"Providers & Backends","summary":"Provider contracts, backend resolution, local vs remote Ollama, and the environment knobs that shape LLM runtime behavior.","opensInNewTab":false,"href":"/demo/llm/providers"},{"section":"llm","slug":"skills","label":"LLM Module","title":"Adding Skills","summary":"How a console command becomes AI-executable through `#[AsAiSkill]`, metadata policy, and registry discovery.","opensInNewTab":false,"href":"/demo/llm/skills"},{"section":"llm","slug":"execution-flow","label":"LLM Module","title":"Execution Flow","summary":"How a user request becomes a planner decision, a reviewed skill proposal, and finally a real console execution.","opensInNewTab":false,"href":"/demo/llm/execution-flow"}],"groups":[{"key":"assistant-basics","label":"Assistant Surface","featureCount":2,"features":[{"section":"llm","slug":"overview","label":"LLM Module","title":"LLM Module Overview","summary":"What `semitexa/llm` adds to the framework and how your project can expose its own CLI skills to the assistant.","opensInNewTab":false,"href":"/demo/llm/overview"},{"section":"llm","slug":"providers","label":"LLM Module","title":"Providers & Backends","summary":"Provider contracts, backend resolution, local vs remote Ollama, and the environment knobs that shape LLM runtime behavior.","opensInNewTab":false,"href":"/demo/llm/providers"}]},{"key":"skill-system","label":"Skill System","featureCount":2,"features":[{"section":"llm","slug":"skills","label":"LLM Module","title":"Adding Skills","summary":"How a console command becomes AI-executable through `#[AsAiSkill]`, metadata policy, and registry discovery.","opensInNewTab":false,"href":"/demo/llm/skills"},{"section":"llm","slug":"execution-flow","label":"LLM Module","title":"Execution Flow","summary":"How a user request becomes a planner decision, a reviewed skill proposal, and finally a real console execution.","opensInNewTab":false,"href":"/demo/llm/execution-flow"}]}]},{"key":"testing","label":"Testing","summary":"Contract-level verification patterns for payloads and other framework boundaries.","icon":"QA","eyebrow":"Verification","starter":false,"prerequisites":["routing"],"featureCount":1,"href":"/demo/testing","features":[{"section":"testing","slug":"payload-contracts","label":"Testing","title":"Payload Contract Testing","summary":"Run one project-level contract suite through the canonical test runner and let strategy profiles verify payload boundaries without hand-writing repetitive negative cases.","opensInNewTab":false,"href":"/demo/testing/payload-contracts"}],"groups":[{"key":"contracts","label":"Contracts","featureCount":1,"features":[{"section":"testing","slug":"payload-contracts","label":"Testing","title":"Payload Contract Testing","summary":"Run one project-level contract suite through the canonical test runner and let strategy profiles verify payload boundaries without hand-writing repetitive negative cases.","opensInNewTab":false,"href":"/demo/testing/payload-contracts"}]}]}]}],"currentSection":"testing","currentSlug":"payload-contracts","infoWhat":"Automated contract testing for payloads — #[TestablePayload] marks a payload for strategy-based validation.","infoHow":"The testing framework discovers testable payloads, applies strategy profiles (Standard, Strict, Paranoid), and runs security, type enforcement, and monkey testing strategies against each endpoint.","infoWhy":"Contract tests verify that payloads reject bad input and accept good input without writing individual test cases. Strategy profiles let teams choose their risk tolerance.","infoKeywords":[{"term":"#[TestablePayload]","definition":"Marks a payload for automated contract testing with configurable strategies."},{"term":"MonkeyTestingStrategy","definition":"Sends random/malformed input to test endpoint robustness."}],"navMode":"catalog","activeLayerKey":"full-catalog","authUi":{"isAuthenticated":false,"label":"Google user","shortLabel":"Google","email":null,"startUrl":"/demo/auth/google/start?return_to=%2Fdemo","authPageUrl":"/demo/auth/google?return_to=%2Fdemo","accountUrl":"/demo/auth/google?return_to=%2Fdemo","logoutUrl":"/demo/auth/google/logout?return_to=%2Fdemo","actionLabel":"Sign in with Google","signInTitle":"Sign in with Google to unlock advanced demos","signedInLabel":"Authorized as"},"explanation":{"what":"Automated contract testing for payloads — #[TestablePayload] marks a payload for strategy-based validation.","how":"The testing framework discovers testable payloads, applies strategy profiles (Standard, Strict, Paranoid), and runs security, type enforcement, and monkey testing strategies against each endpoint.","why":"Contract tests verify that payloads reject bad input and accept good input without writing individual test cases. Strategy profiles let teams choose their risk tolerance.","keywords":[{"term":"#[TestablePayload]","definition":"Marks a payload for automated contract testing with configurable strategies."},{"term":"MonkeyTestingStrategy","definition":"Sends random/malformed input to test endpoint robustness."}]},"sourceCode":{"#[TestablePayload]":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Semitexa\\Testing\\Attributes;\n\nuse Attribute;\n\n/**\n * Declares testing strategies for a PayloadDTO class.\n *\n * Intentionally separate from #[AsPayload] to avoid loading test classes\n * during production bootstrap (SRP — routing ≠ testing).\n *\n * Example:\n * ```php\n * #[AsProtectedPayload(path: '/api/payments', methods: ['POST'])]\n * #[TestablePayload(\n *     strategies: [ParanoidProfileStrategy::class],\n *     context: [\n *         'auth_header'    => 'Authorization',\n *         'auth_scheme'    => 'Bearer',\n *         'token_provider' => TestTokenProvider::class,\n *     ]\n * )]\n * class PaymentPayload implements PayloadInterface { }\n * ```\n *\n * The `context` map is passed as-is to each strategy via PayloadMetadata::$context.\n * Each strategy reads only the keys it cares about; unknown keys are ignored.\n */\n#[Attribute(Attribute::TARGET_CLASS)]\nfinal class TestablePayload\n{\n    /**\n     * @param list<class-string<\\Semitexa\\Testing\\Contract\\TestingStrategyInterface>> $strategies\n     * @param array<string, mixed> $context  Per-strategy configuration (auth tokens, flags, etc.)\n     */\n    public function __construct(\n        public readonly array $strategies = [],\n        public readonly array $context = [],\n    ) {}\n}\n","PayloadContractTester":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Semitexa\\Testing;\n\nuse Semitexa\\Testing\\Contract\\TestingProfileInterface;\nuse Semitexa\\Testing\\Contract\\TestingStrategyInterface;\nuse Semitexa\\Testing\\Contract\\TransportInterface;\nuse Semitexa\\Testing\\Data\\ContractTestResult;\nuse Semitexa\\Testing\\Data\\PayloadMetadata;\nuse Semitexa\\Testing\\Data\\StrategyResult;\n\n/**\n * Orchestrates the execution of all declared strategies against a PayloadDTO.\n *\n * Execution model:\n *   - Sequential strategy execution (predictable output order).\n *   - Fail-safe by default: all strategies run even if one fails.\n *   - Fail-fast: configurable via context['fail_fast'] = true.\n *   - Profiles are expanded into sub-strategies before execution (recursive, deduplicated).\n *   - Skipped strategies (canRun() = false) are reported as SKIPPED, not FAILED.\n */\nfinal class PayloadContractTester\n{\n    public function __construct(\n        private readonly TransportInterface $transport,\n        private readonly FailureReporter $reporter,\n    ) {}\n\n    public function run(PayloadMetadata $metadata): ContractTestResult\n    {\n        $failFast = (bool) ($metadata->context['fail_fast'] ?? false);\n        $strategies = $this->resolveStrategies($metadata->strategies);\n        $results = [];\n\n        foreach ($strategies as $strategyClass) {\n            $strategy = new $strategyClass();\n\n            if (!$strategy->canRun($metadata)) {\n                $results[] = StrategyResult::skipped($strategyClass, $strategy->skipReason($metadata));\n                continue;\n            }\n\n            foreach ($strategy->generateCases($metadata) as $case) {\n                $response = $this->transport->send($case);\n\n                try {\n                    $strategy->assertResponse($case, $response);\n                    $results[] = StrategyResult::passed($strategyClass, $case);\n                } catch (\\PHPUnit\\Framework\\AssertionFailedError $e) {\n                    $this->reporter->report($metadata, $strategy, $case, $response, $e);\n                    $results[] = StrategyResult::failed($strategyClass, $case, $e->getMessage());\n\n                    if ($failFast) {\n                        return new ContractTestResult($results);\n                    }\n                }\n            }\n        }\n\n        return new ContractTestResult($results);\n    }\n\n    /**\n     * Expand profiles into their constituent strategies recursively.\n     * Deduplicates: each strategy class runs at most once.\n     *\n     * @param list<class-string<TestingStrategyInterface>> $strategyClasses\n     * @return list<class-string<TestingStrategyInterface>>\n     */\n    private function resolveStrategies(array $strategyClasses): array\n    {\n        $resolved = [];\n        $seen = [];\n\n        $expand = function(array $classes) use (&$expand, &$resolved, &$seen): void {\n            foreach ($classes as $class) {\n                if (isset($seen[$class])) {\n                    continue;\n                }\n                $seen[$class] = true;\n\n                if (!class_exists($class)) {\n                    continue;\n                }\n\n                $instance = new $class();\n                if ($instance instanceof TestingProfileInterface) {\n                    // Expand profile into sub-strategies (recursive)\n                    $expand($instance->getStrategyClasses());\n                } else {\n                    $resolved[] = $class;\n                }\n            }\n        };\n\n        $expand($strategyClasses);\n        return $resolved;\n    }\n}\n","test:run Command":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Semitexa\\Testing\\Application\\Console\\Command;\n\nuse Semitexa\\Core\\Attribute\\AsCommand;\nuse Semitexa\\Core\\Console\\BaseCommand;\nuse Symfony\\Component\\Console\\Command\\Command;\nuse Symfony\\Component\\Console\\Input\\InputArgument;\nuse Symfony\\Component\\Console\\Input\\InputInterface;\nuse Symfony\\Component\\Console\\Output\\OutputInterface;\nuse Symfony\\Component\\Console\\Style\\SymfonyStyle;\nuse Symfony\\Component\\Process\\Process;\n\n#[AsCommand(\n    name: 'test:run',\n    description: 'PHPUnit-only fallback (Docker, APP_ENV=dev). Use bin/semitexa test:run for the full PHPUnit + E2E pipeline.',\n)]\nclass TestRunCommand extends BaseCommand\n{\n    protected function configure(): void\n    {\n        $this->setName('test:run')\n            ->setDescription(\n                'PHPUnit-only fallback (Docker, APP_ENV=dev). '\n                . 'Canonical entry point is bin/semitexa test:run, which adds the E2E (Playwright) phase.',\n            )\n            ->addArgument(\n                'phpunit-args',\n                InputArgument::OPTIONAL | InputArgument::IS_ARRAY,\n                'Arguments forwarded to PHPUnit (e.g. -- --testdox --filter MyTest)',\n            );\n    }\n\n    protected function execute(InputInterface $input, OutputInterface $output): int\n    {\n        $io = new SymfonyStyle($input, $output);\n        $projectRoot = $this->getProjectRoot();\n\n        if (!$this->isDevEnvironment($projectRoot)) {\n            $io->error('test:run is only available when APP_ENV=dev.');\n            $io->text('Set APP_ENV=dev in your .env file to enable test containers.');\n            return Command::FAILURE;\n        }\n\n        if (!file_exists($projectRoot . '/docker-compose.yml')) {\n            $io->error('docker-compose.yml not found. Run from project root.');\n            return Command::FAILURE;\n        }\n\n        if (!file_exists($projectRoot . '/docker-compose.test.yml')) {\n            $io->error('docker-compose.test.yml not found.');\n            return Command::FAILURE;\n        }\n\n        $composeFiles = $this->buildComposeFiles($projectRoot);\n        $phpunitArgs  = $input->getArgument('phpunit-args') ?? [];\n\n        $io->title('Running PHPUnit in Docker');\n        $io->text('Compose files: ' . implode(', ', $composeFiles));\n\n        $cmd = array_merge(\n            ['docker', 'compose'],\n            $this->buildComposeFileArgs($composeFiles),\n            ['run', '--rm', 'phpunit', 'vendor/bin/phpunit'],\n            $phpunitArgs,\n        );\n\n        $process = new Process($cmd, $projectRoot);\n        $process->setTimeout(null);\n        $process->run(function (string $type, string $buffer) use ($output): void {\n            $output->write($buffer);\n        });\n\n        return $process->isSuccessful() ? Command::SUCCESS : Command::FAILURE;\n    }\n\n    /**\n     * Always: docker-compose.yml + docker-compose.test.yml.\n     * Conditionally: mysql and redis overlays (mirrors ServerStartCommand logic).\n     *\n     * @return list<string>\n     */\n    private function buildComposeFiles(string $projectRoot): array\n    {\n        $files = ['docker-compose.yml'];\n\n        if ($this->shouldUseMysqlCompose($projectRoot)) {\n            $files[] = 'docker-compose.mysql.yml';\n        }\n        if ($this->shouldUseRedisCompose($projectRoot)) {\n            $files[] = 'docker-compose.redis.yml';\n        }\n\n        $files[] = 'docker-compose.test.yml';\n\n        return $files;\n    }\n\n    /**\n     * @param list<string> $files\n     * @return list<string>\n     */\n    private function buildComposeFileArgs(array $files): array\n    {\n        $args = [];\n        foreach ($files as $file) {\n            $args[] = '-f';\n            $args[] = $file;\n        }\n        return $args;\n    }\n\n    private function isDevEnvironment(string $projectRoot): bool\n    {\n        $content = $this->readEnv($projectRoot);\n        return $content !== null\n            && (bool) preg_match('/^\\s*APP_ENV\\s*=\\s*dev\\s*$/mi', $content);\n    }\n\n    private function shouldUseMysqlCompose(string $projectRoot): bool\n    {\n        if (!file_exists($projectRoot . '/docker-compose.mysql.yml')) {\n            return false;\n        }\n        $content = $this->readEnv($projectRoot);\n        return $content !== null && (bool) preg_match('/^\\s*DB_DRIVER\\s*=\\s*\\S+/m', $content);\n    }\n\n    private function shouldUseRedisCompose(string $projectRoot): bool\n    {\n        if (!file_exists($projectRoot . '/docker-compose.redis.yml')) {\n            return false;\n        }\n        $content = $this->readEnv($projectRoot);\n        return $content !== null && (bool) preg_match('/^\\s*REDIS_HOST\\s*=\\s*\\S+/m', $content);\n    }\n\n    private function readEnv(string $projectRoot): ?string\n    {\n        $envFile = $projectRoot . '/.env';\n        if (!file_exists($envFile)) {\n            return null;\n        }\n        $content = file_get_contents($envFile);\n        return $content !== false ? $content : null;\n    }\n}\n","StrictProfileStrategy":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Semitexa\\Testing\\Strategy\\Profile;\n\nuse Semitexa\\Testing\\Contract\\TestingProfileInterface;\nuse Semitexa\\Testing\\Data\\PayloadMetadata;\nuse Semitexa\\Testing\\Data\\ResponseResult;\nuse Semitexa\\Testing\\Data\\TestCaseDescriptor;\nuse Semitexa\\Testing\\Strategy\\HttpMethodStrategy;\nuse Semitexa\\Testing\\Strategy\\SecurityStrategy;\nuse Semitexa\\Testing\\Strategy\\TypeEnforcementStrategy;\n\n/**\n * Strict profile: Standard + aggressive type mutation testing.\n *\n * Forces type_mutation to true even if context overrides it to false.\n * Inject context override via PayloadContractTester before expansion.\n */\nfinal class StrictProfileStrategy implements TestingProfileInterface\n{\n    public function getStrategyClasses(): array\n    {\n        return [\n            SecurityStrategy::class,\n            HttpMethodStrategy::class,\n            TypeEnforcementStrategy::class, // type_mutation defaults to true\n        ];\n    }\n\n    public function canRun(PayloadMetadata $metadata): bool\n    {\n        return false;\n    }\n\n    public function skipReason(PayloadMetadata $metadata): string\n    {\n        return 'StrictProfileStrategy is a profile — it is always expanded into sub-strategies.';\n    }\n\n    public function generateCases(PayloadMetadata $metadata): iterable\n    {\n        return [];\n    }\n\n    public function assertResponse(TestCaseDescriptor $case, ResponseResult $response): void {}\n}\n","ParanoidProfileStrategy":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Semitexa\\Testing\\Strategy\\Profile;\n\nuse Semitexa\\Testing\\Contract\\TestingProfileInterface;\nuse Semitexa\\Testing\\Data\\PayloadMetadata;\nuse Semitexa\\Testing\\Data\\ResponseResult;\nuse Semitexa\\Testing\\Data\\TestCaseDescriptor;\nuse Semitexa\\Testing\\Strategy\\HttpMethodStrategy;\nuse Semitexa\\Testing\\Strategy\\MonkeyTestingStrategy;\nuse Semitexa\\Testing\\Strategy\\SecurityStrategy;\nuse Semitexa\\Testing\\Strategy\\TypeEnforcementStrategy;\n\n/**\n * Paranoid profile: everything — Standard + type mutations + MonkeyTesting.\n * Maximum coverage. Use for critical endpoints (auth, payments, data mutation).\n */\nfinal class ParanoidProfileStrategy implements TestingProfileInterface\n{\n    public function getStrategyClasses(): array\n    {\n        return [\n            SecurityStrategy::class,\n            HttpMethodStrategy::class,\n            TypeEnforcementStrategy::class,\n            MonkeyTestingStrategy::class,\n        ];\n    }\n\n    public function canRun(PayloadMetadata $metadata): bool\n    {\n        return false;\n    }\n\n    public function skipReason(PayloadMetadata $metadata): string\n    {\n        return 'ParanoidProfileStrategy is a profile — it is always expanded into sub-strategies.';\n    }\n\n    public function generateCases(PayloadMetadata $metadata): iterable\n    {\n        return [];\n    }\n\n    public function assertResponse(TestCaseDescriptor $case, ResponseResult $response): void {}\n}\n","MonkeyTestingStrategy":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Semitexa\\Testing\\Strategy;\n\nuse PHPUnit\\Framework\\Assert;\nuse Semitexa\\Testing\\Contract\\TestingStrategyInterface;\nuse Semitexa\\Testing\\Data\\PayloadMetadata;\nuse Semitexa\\Testing\\Data\\ResponseResult;\nuse Semitexa\\Core\\Http\\HttpStatus;\nuse Semitexa\\Testing\\Data\\TestCaseDescriptor;\n\n/**\n * Sends chaotic / garbage requests and verifies the system returns a safe 4xx,\n * never a 5xx (500, 502, 503, 504).\n *\n * Configurable via context:\n *   monkey_reject_statuses: list<int>  — statuses considered failures (default: 5xx range)\n *   monkey_accept_statuses: list<int>  — statuses considered safe (default: 4xx range)\n *   If neither is set, any non-5xx is considered safe.\n */\nfinal class MonkeyTestingStrategy implements TestingStrategyInterface\n{\n    private const DEFAULT_REJECT = [HttpStatus::InternalServerError->value, HttpStatus::BadGateway->value, HttpStatus::ServiceUnavailable->value, HttpStatus::GatewayTimeout->value];\n\n    public function canRun(PayloadMetadata $metadata): bool\n    {\n        return true;\n    }\n\n    public function skipReason(PayloadMetadata $metadata): string\n    {\n        return '';\n    }\n\n    public function generateCases(PayloadMetadata $metadata): iterable\n    {\n        $method = $this->pickWriteMethod($metadata->methods);\n        $path = $metadata->path;\n\n        yield new TestCaseDescriptor(\n            description: 'Huge JSON body (~1 MB) → safe rejection',\n            method: $method,\n            path: $path,\n            headers: ['Content-Type' => 'application/json'],\n            body: str_repeat('{\"a\":\"' . str_repeat('x', 100) . '\",', 10_000) . '\"z\":\"end\"}',\n            expectedStatus: $this->safeStatusRange($metadata),\n        );\n\n        yield new TestCaseDescriptor(\n            description: 'Deeply nested array (depth 500) → safe rejection',\n            method: $method,\n            path: $path,\n            headers: [],\n            body: $this->buildDeeplyNested(500),\n            expectedStatus: $this->safeStatusRange($metadata),\n        );\n\n        yield new TestCaseDescriptor(\n            description: 'Malformed JSON → safe rejection',\n            method: $method,\n            path: $path,\n            headers: ['Content-Type' => 'application/json'],\n            body: '{not valid json at all ][[]',\n            expectedStatus: $this->safeStatusRange($metadata),\n        );\n\n        yield new TestCaseDescriptor(\n            description: 'SQLi-like string in all fields → safe rejection',\n            method: $method,\n            path: $path,\n            headers: [],\n            body: $this->buildSqliPayload($metadata),\n            expectedStatus: $this->safeStatusRange($metadata),\n        );\n\n        yield new TestCaseDescriptor(\n            description: 'Null bytes and control characters → safe rejection',\n            method: $method,\n            path: $path,\n            headers: [],\n            body: $this->buildNullBytePayload($metadata),\n            expectedStatus: $this->safeStatusRange($metadata),\n        );\n\n        yield new TestCaseDescriptor(\n            description: 'Extreme Unicode (emoji, RTL, surrogates) → safe rejection',\n            method: $method,\n            path: $path,\n            headers: [],\n            body: $this->buildUnicodePayload($metadata),\n            expectedStatus: $this->safeStatusRange($metadata),\n        );\n    }\n\n    public function assertResponse(TestCaseDescriptor $case, ResponseResult $response): void\n    {\n        $rejectStatuses = $case->expectedStatus;\n        if (is_array($rejectStatuses)) {\n            // expectedStatus here is the SAFE set\n            Assert::assertContains(\n                $response->statusCode,\n                $rejectStatuses,\n                \"[MonkeyTestingStrategy] {$case->description}: got unsafe status {$response->statusCode}.\"\n            );\n        } else {\n            Assert::assertNotSame(\n                $case->expectedStatus,\n                $response->statusCode,\n                \"[MonkeyTestingStrategy] {$case->description}: got status {$response->statusCode}.\"\n            );\n        }\n    }\n\n    /** @return list<int> Safe (accepted) status codes */\n    private function safeStatusRange(PayloadMetadata $metadata): array\n    {\n        if (isset($metadata->context['monkey_accept_statuses'])) {\n            return $metadata->context['monkey_accept_statuses'];\n        }\n        $reject = $metadata->context['monkey_reject_statuses'] ?? self::DEFAULT_REJECT;\n        return array_values(array_diff(range(100, 599), $reject));\n    }\n\n    private function buildDeeplyNested(int $depth): array\n    {\n        $nested = ['leaf' => true];\n        for ($i = 0; $i < $depth; $i++) {\n            $nested = ['child' => $nested];\n        }\n        return $nested;\n    }\n\n    private function buildSqliPayload(PayloadMetadata $metadata): array\n    {\n        $sqli = \"'; DROP TABLE users; --\";\n        $body = [];\n        foreach ($metadata->properties as $prop) {\n            $body[$prop->name] = $sqli;\n        }\n        return $body ?: ['q' => $sqli];\n    }\n\n    private function buildNullBytePayload(PayloadMetadata $metadata): array\n    {\n        $evil = \"normal\\x00null\\x01\\x02\\x03byte\";\n        $body = [];\n        foreach ($metadata->properties as $prop) {\n            $body[$prop->name] = $evil;\n        }\n        return $body ?: ['q' => $evil];\n    }\n\n    private function buildUnicodePayload(PayloadMetadata $metadata): array\n    {\n        $unicode = \"\\u{1F4A9}\\u{202E}reversed\\u{FEFF}bom\\u{D800}\";\n        $body = [];\n        foreach ($metadata->properties as $prop) {\n            $body[$prop->name] = $unicode;\n        }\n        return $body ?: ['q' => $unicode];\n    }\n\n    private function pickWriteMethod(array $methods): string\n    {\n        $methods = array_map('strtoupper', $methods);\n        foreach (['POST', 'PUT', 'PATCH', 'GET'] as $preferred) {\n            if (in_array($preferred, $methods, true)) {\n                return $preferred;\n            }\n        }\n        return $methods[0] ?? 'POST';\n    }\n}\n"},"resultPreviewTemplate":"@project-layouts-semitexa-demo/components/previews/cli-command-workbench.html.twig","resultPreviewData":{"eyebrow":"Contract Testing","title":"One scaffolded suite can exercise every testable payload","summary":"Instead of hand-writing the same validation and bad-input cases for every endpoint, Semitexa lets payloads declare their testing strategies and runs the composed suite through a shared transport.","pillars":[{"title":"Scaffold once.","summary":"test:init creates the universal ProjectPayloadsContractTest that auto-discovers payloads marked with #[TestablePayload]."},{"title":"Choose a profile.","summary":"Strict and Paranoid profiles expand into concrete strategies like method checks, type mutation, security assertions, and monkey input."},{"title":"Run through real transport.","summary":"The contract tester sends actual requests and reports which strategy or generated case failed instead of hiding everything in custom helpers."}],"commands":[{"name":"bin/semitexa test:init","purpose":"Scaffold the universal payload contract test into tests/Payload.","value":"Gives the project one canonical entrypoint for payload boundary verification."},{"name":"bin/semitexa test:run -- --filter ProjectPayloadsContractTest","purpose":"Run only the generated contract suite inside the dev Docker stack.","value":"Keeps feedback focused while expanding coverage across all marked payloads."},{"name":"bin/semitexa test:run -- --testdox","purpose":"Forward normal PHPUnit flags through the Semitexa wrapper.","value":"Developers keep PHPUnit ergonomics while preserving the project container setup."}],"snippets":[{"label":"Mark a payload as contract-testable","code":"#[AsPublicPayload(path: '/api/payments', methods: ['POST'])]\n#[TestablePayload(\n    strategies: [StrictProfileStrategy::class, ParanoidProfileStrategy::class],\n    context: ['fail_fast' => false],\n)]\nfinal class PaymentPayload {}"},{"label":"Scaffold and run the suite","code":"bin/semitexa test:init\nbin/semitexa test:run -- --filter ProjectPayloadsContractTest"},{"label":"What Paranoid profile expands to","code":"ParanoidProfileStrategy::class => [\n    SecurityStrategy::class,\n    HttpMethodStrategy::class,\n    TypeEnforcementStrategy::class,\n    MonkeyTestingStrategy::class,\n]"}]},"l2ContentTemplate":"@project-layouts-semitexa-demo/components/previews/checklist-panel.html.twig","l2ContentData":{"eyebrow":"Testing Posture","title":"When this pays off the most","summary":"Contract testing is strongest when the payload really is the transport boundary and the team wants broad negative coverage without a pile of custom boilerplate.","rules":["Use Strict or Paranoid profiles on auth, money, or mutation endpoints where malformed input should never leak into handler logic.","Keep the payload as the real boundary object; contract testing loses value if the handler still parses raw arrays or globals.","Prefer one generated suite plus strategy profiles over dozens of repetitive “422 when field is missing” tests.","Let the contract suite complement domain tests, not replace them; payload robustness and business behavior are different concerns."]},"__page_document_html_iri":"/demo/testing/payload-contracts","__page_document_json_iri":"/demo/testing/payload-contracts?_format=json","__page_alternates":[{"type":"application/json","href":"/demo/testing/payload-contracts?_format=json"}]}