marlocDiscover · .project/artifacts

marlocHomes

An independent property application — listings, agents, saved searches, favourites, leads and moderation. Not a feature of Discover: its own data model, its own agent and consumer identity, its own manifest. This page exists because its front door is the application, behind Cloudflare Access, so an internal reader who is not signed in sees a login screen and learns nothing.

19 August 2026 ADR-0009 B-model · ADR-0010 · AUTH-003 3,013 properties · 403 agents

The composition contract

Every public page is interpreted, not written

There is no hand-composed public page left in Homes, and no fallback JSX to fall back to. That is the single most consequential thing about it, and everything below follows from it.

1 · The manifest

Bundled snapshot, live row

src/site-manifest.ts is the committed snapshot; manifest_active in D1 is the live one, resolved fail-open — a corrupt or missing row falls back to the bundle rather than taking the site down.

src/site-manifest.ts · D1 manifest_active

2 · The route

Renders one thing and nothing else

A route renders <InterpretedRoute>. Its loader owns behaviour — queries, auth, actions — and hands results to the record channel; the manifest owns which bands appear and in what order.

app/routes/*.tsx

3 · The band

Three deliberate edits, or a compile error

Adding one means the component, then src/interpretable-keys.ts, then the manifest. The interpreter types its key→component map against that list, so a gap is a compile error, and a test fails if the manifest declares a key nothing renders.

src/interpretable-keys.ts · app/lib/manifest-interpreter.tsx

4 · The catalog

Canonical first, always

A registry key must exist in the design-system catalog before Homes emits it. Canonical first, then the mirror — never the reverse. The validator refused a band here for referencing a render Homes does not declare, and it was right to.

.marlocDesignSystem-root · marloc-md-design-system-worker

The rules that hold it together

Four, each written after something went wrong

No fabricated data

Every value a visitor sees traces to a real column, a taxonomy row, the field catalog, moderated editorial content, or manifest copy. This is not stylistic: the detail page once rendered a table of invented Kenmerken for two demo listings, and the field catalog's own example strings — "3 vergelijkbare woningen" — as though they were that property's data. A planned catalog entry resolves to nothing and therefore shows nothing.

Two i18n systems, one rule each

Composition copy — anything a composer can change — lives in the manifest as LocalizedText. Fixed chrome — labels no composer owns — lives in app/lib/i18n.ts behind t(locale, key). Nothing may live in neither, and an inline locale === "nl" ? … : … ternary is a third pattern that is not allowed.

The theme chain has three layers and one direction

Base tokens, then the shared platform theme fetched over the DISCOVER binding (fail-open to ""), then the active manifest's tokens emitted last — so a moderated token edit wins without a deploy. Manifest values are moderated data interpolated into a <style> tag, so they are sanitised, and that sanitiser has tests.

Identity is AccountRevenue's, data is ours

Both consumer logins moved to ARC under AUTH-003. ARC proves who someone is; Homes keeps what that identity owned — favourites, saved searches, leads. Existing rows were adopted by email on first ARC sign-in rather than migrated, so a row never adopted is a person who never came back, not data left behind. The buyer is a second ARC app, not a flag on the agent's.

What it offers other workers

A menu, never a query surface

A render is a declared query shape, never live search. A consumer picks from the menu, which is what caps the cost per call at the render's own limit and stops anyone composing a question Homes never agreed to answer.

RenderSubjectShapeBound
r-property-resultssettiles50
r-newest-propertiessetrail8
r-area-listingsset, keyed on citytiles12
r-agent-listingsowner-set, keyed on agent_idtiles6
r-blog-teasers · r-blog-indexsettiles · list3 · 12
r-property-detail · r-blog-articleonedetail1

An absent header is not a zero

A fragment answers with X-Marloc-Rows, and that header is omitted on every failure path. Number('') is 0, so a consumer reading it carelessly reports "asked, found none" for a call that never completed. This fleet shipped that bug twice before it was pinned in both consumers.

Honest limits

What is not there

  • No link to a Discover business. The agents table carries a business_name string and no Discover id, so "this agent's profile over there" cannot be answered — and matching on the name would silently merge two firms that share one.
  • No public hostname without Access. workers_dev is off and the host is gated, which is why this artifact exists.
  • No content permissions. Every published record is public.

Sources: apps/marloc-hm-homes-worker/CLAUDE.md · .project/HOMES/plans/MASTER-PLAN.md · src/site-manifest.ts · the remote D1. Counts were read from the live database on 19 August 2026, not estimated.