Skip to main content
@craft-ts
LearnLearn with EffectAI agentsGuideReference
@craft-ts/core@craft-ts/component@craft-ts/dev-tools@craft-ts/effect@craft-ts/style@craft-ts/style-testing@craft-ts/i18n@craft-ts/i18n-effect@craft-ts/mcp
ExamplesMigrationEffect compatibility & maturityAdopting CraftTS progressivelyPress kitRoadmapBacklog
Beta
  • Learn
  • Learn with Effect
  • AI agents
  • Guide
  • Reference
  • Packages
  • Resources
  • Guide overview
  • Create a project
i.Core concepts
  • The mental model
  • Which primitive should I use?
  • Anatomy of a primitive
  • Generators and yield*
  • Insertions
  • Typed insertion pipes
  • Exceptions as values
ii.Managing state
  • Local state
  • State machines
  • query
  • Mutations
  • queryParams
  • asyncProcess
  • Selecting
  • Reacting to mutations
  • Collections
  • Collection utilities
  • Persistence
  • GlobalPersisterHandler
  • Pagination placeholders
  • Schema validation
iii.Recommended approaches
  • Inject at the point of use
iv.Structuring the app
  • craftService
  • Service scopes
  • Shaping the public API
  • Abstract services
  • App start
  • Lazy services
  • Server functions
  • craftRegisterFor
  • Target wrapper
v.Routing & type-safe DI
  • Setup
  • CLI automation
  • ESLint rules
  • Route providers
  • Route guards
  • Route exception handling
  • Non-blocking navigation
  • Global error component
  • Route load errors
  • Scaling routes
vi.Components & templates
  • Components
  • Activating the style system
  • Typed styles
  • Defining a design system
  • Tokens and variables
  • Global foundation and fonts
  • Pseudo-elements and animations
  • Axes and the matrix
  • Context obligations
  • Testing visual states
  • Text contrast
  • Visual attestation
  • Template obligations
  • Typed CSS variables
  • Fine-grained reactivity
  • Progressive rendering
  • Directives and .pipe(...)
  • settledValue & pendingNode
  • Accessibility
  • Customization
  • Content projection
  • Styling a component
  • Template migrator
vii.Forms
  • Overview
  • Validators
  • Submitting
  • Nested forms
  • Exception handling
  • Complete examples
viii.Testing
  • Testing services
  • Testing components
  • Type-level tests
  • Browser boundaries
  • Page stability
  • Architecture rules
    • Overview
    • Declarative baseline
    • Unique identities
    • HTTP endpoint ownership
    • Computed purity
    • Dependency cycles
    • Mutation reactions
    • Route DI proofs
    • Route component files
    • Path boundaries
    • Exclusive links
    • Persisted identities
    • insertSelect keys
    • craftEffect and network
    • craftEffect and imperative sync
    • Interactive element names
    • Metric thresholds
    • Server-state loaders
    • Primitive loader requirements
  • Extensible architecture graph
  • Graph insights
  • Craft graph vs Nx
ix.Reactivity utilities
  • craftComputed
  • craftEffect
  • craftMethod
  • source$
  • on$
  • fromEventToSource$
  • sourceFromEvent
  • afterRecomputation
x.Internationalisation
  • Type-safe i18n
  • The catalogue
  • Tokens
  • The runtime
  • With Effect
xi.Deployment (experimental)
  • Overview
  • Manifest reference
  • Diagnostics
  • Providers
  • Alchemy provider
xii.Going further
  • SSR and hydration
  • Effect integration
  • Program operators
  • Pattern matching
  • Observability
  • AI agents
  • Temporal runtime
  1. Components & templates
  2. Context obligations

Context obligations#

Level 3. Adopted per whole route, and that granularity is the whole point of this page.

What it is for#

A sticky element needs a scroll port. A scroll-state query needs an element declaring the container. Get either wrong and nothing errors — the component simply never appears, or sticks to the wrong box, and you find out in a screenshot weeks later.

ts
import {
  craftStyles,
  display,
  insetBlockEnd,
  position,
  provides,
  requires,
  scrollPort,
  space,
} from '@craft-ts/style';

export const backToTop = craftStyles('backToTop', {
  anchor: [
    requires(scrollPort.block),
    position.sticky,
    insetBlockEnd(space(4)),
  ],
});

export const shell = craftStyles('appShell', {
  main: [provides(scrollPort.block), display.block],
});

requires is attached to the class, not the sheet, so the error names a rule rather than a file. And provides(...) returns the CSS effect and the discharge in the same object: since overflow is not in the property table, this is the only road to overflow-block: auto. Claiming to provide without laying the CSS is not something anyone can write.

Where it becomes an error#

Nowhere, until a component seals:

ts
import { craftComponent } from '@craft-ts/component';

craftComponent('AppShell', { seals: [true] }, template);

Until then the requirement travels — an ancestor still has the right to answer it, and complaining early would be wrong. Sealing says "from here up, nobody will".

Remove the provider and the typecheck fails:

ERROR_unmet_context_requirement: "'scrollPort.block' is required by this subtree and nothing above it provides one. declare it on the layout component that owns the scrollable area. An overflow on the direct parent would create a second scroll port, and the sticky element would stick to the wrong container."

What is missing, where to put it, and what the obvious wrong fix would do.

Why the granularity is the route#

The requirement is carried by the type of the render tree. It crosses a component boundary only where the tree is typed all the way through. One component in the path that hands back a loosely typed subtree, and the demand stops travelling — silently, because nothing is wrong with that component.

So level 3 is not something you get for the components you migrated. You get it for a route once the route is migrated, and not before. The dependency graph reports which components are not covered rather than reporting a clean bill:

ts
import { extractionGaps, undischargedObligations } from '@craft-ts/dev-tools';

extractionGaps(graph); // components no sheet is known to style
undischargedObligations(graph); // required somewhere, discharged nowhere

The marked way out#

ts
import { scrollPort, unsafeAssume } from '@craft-ts/style';

unsafeAssume(scrollPort.block, 'the host page owns the scroll port');

Discharges without laying the CSS, for the cases the model cannot see — a shell owned by someone else. It propagates unproven, so the graph counts it as debt. An escape hatch that did not bubble up would be a design bug, not a convenience.

←Axes and the matrixTesting visual states→

On this page

  • What it is for
  • Where it becomes an error
  • Why the granularity is the route
  • The marked way out

@craft-ts · MIT licensed

GitHubnpm

Search