App ComposerLoftwah’s instance

Walkthrough · · 12 min read

Building App Composer,
and building with it.

A real brief, a local Worker run, and one guestbook note that earns its place. This is how App Composer is put together, and how two independent apps learned to agree about a contribution without merging their data.

Flat orange build-story graphic with the white App Composer mark and three steps: a real brief with limits, two independent Workers running locally, and one guestbook note that earns its place.
The article hero has a deliberate phone crop; the separate 1200 × 630 share card keeps its own composition. Editable HTML and export commands live in .artwork/building/.

The finished result comes first

A visitor opens a Linkarooie profile and sees four curated links, a short explanation, and one clear action: Leave a note in the guestbook. The action opens Here / there's real postcard form. The note becomes a pending, private record. The owner approves it in Guestbook. Only then does Linkarooie offer it as a separate featuring choice. Hiding it at the source removes the card and media on the next request.

Fictional Linkarooie profile with the Leave a note guestbook panel, two featured authored links and a featured postcard from a fictional visitor.
Local fictional run at 1440 × 1000. The approved note is featured, while the contribution action remains open.
Phone view of the fictional Linkarooie profile with the guestbook invitation, navigation and the start of the featured section.
Same profile at 390 × 844. The jump navigation wraps instead of clipping a label.

These are actual browser captures from two locally running Workers, not mockups. The note, name, links and profile are fictional. The local addresses are visible because the run is local; no hosted deployment is implied.

What App Composer is, and what it is not

App Composer is a place to compose independent apps into one public result. Each app keeps its own source repository, records, media, owner API and release. Composer keeps the workspace, the saved stack, the publication revision and the reviewed connection between them. It is not a second content store, and it does not make every app share one database.

App Composer

Astro pages for the public product, EmDash for native administration, D1 and R2 for Composer's own workspace and publication records, and a small bridge for declared app actions.

Here / there

An independent Cloudflare Worker. It owns postcard submissions, private review, approval, PNG media, source visibility and revocation. Its D1 and R2 data stay with that app.

Linkarooie

Another independent Worker. It owns the profile, authored links, curation and which approved source items are featured. It asks Guestbook for current eligibility on every render.

The agent

A coding agent works in the selected repositories: it reads the brief, prepares the environment, changes source, runs checks and follows the release path. Account access, spending and consequential permissions stay human decisions.

The development history, briefly and accurately

The first commit is 11 October 2026. The working foundation landed on 13 October: the public site, independent composition, Init, the decision explorer, guarded Access administration and a first readiness handoff. The following days added the readiness core, a verified Hello update, the app library and launcher, the composed collection, workspace roles, private outcomes, the enquiry inbox, the owner experience and the approved orange identity. This article describes a change made on 22 October; it is not a retrospective claim that the whole product appeared in one session.

Where the self-build claim is real: Composer's own Init produced the portable specification used to configure the local Linkarooie instance below. This article is itself an Astro page in the Composer repository, built and released through Composer's own checks and guarded deployment. We are not claiming that Composer autonomously wrote its own source, chose its own architecture or deployed without an agent and an owner.

The brief was a question, not a feature list

Why would I want Guestbook entries on Linkarooie? Isn't Guestbook how other people write or comment on my Linkarooie?

The relationship was real but hard to discover. Linkarooie could feature an approved Guestbook moment, yet the profile did not explain where notes come from, who reviews them, or why a featured card is not the same thing as a submission. The easiest wrong answer was a comment box inside Linkarooie. That would duplicate Guestbook's intake, moderation, retention and revocation, and it would create a second place where private visitor text could leak.

The accepted brief separated three states in plain language:

1

Sent for review

The visitor writes a short postcard. It is pending and private. Nothing appears on the profile.

2

Approved in Guestbook

The owner reviews it at the source. Approval makes it public in the Guestbook collection.

3

Featured in Linkarooie

The owner makes a separate curation choice. The card resolves the live source and disappears when that source is hidden.

The action must be honest. If the configured Guestbook is closed, paused, full or unreachable, the profile must not show a send button that leads to a dead form. It also must not expose the guest code, owner credentials or technical abuse controls on the profile. The full acceptance criteria live in #196.

From the brief to a real Init specification

Init is the part of Composer that turns configuration into a portable handoff. For this walkthrough it produced a version 2 composition spec with the title, introduction, separate Guestbook and Linkarooie settings, four authored links and a preview destination. The spec pinned the reviewed app sources it was generated from. It did not contain a credential, and it did not decide how the contribution contract should work.

App Composer Init page configured for a fictional Guestbook and Linkarooie composition, with a portable specification preview.
The real Init form used for the starting composition. The downloaded spec is available as data; it grants no execution or deployment authority.
The specification's operative settings
{
  "preset": "composition",
  "appSettings": {
    "guestbook": {
      "title": "Fictional Trail Notes",
      "theme": "daylight",
      "guestAccess": "postcards"
    },
    "showcase": {
      "title": "Fictional Trail Notes",
      "theme": "daylight",
      "layout": "profile"
    }
  },
  "cards": [
    { "title": "Field notes: the long way round", "url": "https://example.test/field-notes" },
    { "title": "A small toolkit for slow software", "url": "https://example.test/slow-software" },
    { "title": "Weekend radio", "url": "https://example.test/weekend-radio" },
    { "title": "Reading list, autumn", "url": "https://example.test/reading" }
  ],
  "deployment": { "mode": "preview" }
}

The handoff fixed the starting shape. The agent still had to implement the missing cross-app contract, the public state handling, the owner control and the tests. That is the useful division: Init records the decisions a person can review; the agent owns the implementation and the evidence.

The change: ask the source, do not guess

Guestbook already had a public contribution page and an owner review flow. What it lacked was a small, versioned answer to a different question: does this exact Guestbook currently accept guest postcards? The new public endpoint is deliberately boring:

GET /r/:resource/api/invitation

{
  "contract": 1,
  "provider": "guestbook",
  "resource": "…",
  "invitation": {
    "version": 1,
    "kind": "postcard",
    "state": "open | paused | closed | full",
    "accepts": true,
    "configuration": 3,
    "contribute": "https://…/c/3/contribute",
    "collection": "https://…/c/3/",
    "limits": { "message": 160, "alias": 60,
                "width": 1200, "height": 900, "bytes": 100000 }
  }
}

The state follows the resource's current active configuration, not the historical activation path. Paused wins over open. A closed postcard setting is closed. A pending queue at 16 or a non-deleted item count at 200 is full. The same function now controls the Guestbook's own /contribute page, so the source no longer shows a form that would immediately fail. The response contains no guest code, owner token, media or submission text.

Composer's promotion boundary validates the exact manifest descriptor, so a future release cannot quietly add a state or widen the limits:

"guestInvitation": {
  "version": 1,
  "kind": "postcard",
  "route": "/r/:resource/api/invitation",
  "states": ["open", "paused", "closed", "full"],
  "default": "closed",
  "limits": { "message": 160, "alias": 60,
              "width": 1200, "height": 900, "bytes": 100000 }
}

Linkarooie stores one exact { kind, version, resource, configuration } reference. It fetches the invitation through its configured service binding, checks the provider, resource, field set, limits and exact contribution paths, and treats a redirect, foreign origin or partial response as unavailable. The public panel then renders one of five states. Only open produces a send action.

What the owner controls

The native Collect from a saved visitor book control can link, replace or stop the invitation for an exact saved stack instance. Enabling is refused for a hidden or pending book. The same owner boundary exists in the standalone Linkarooie studio. Guestbook still owns approval; Linkarooie still owns the separate featuring choice.

The implementation touched both independent repositories and Composer's bridge:

Local checks: 103 Guestbook tests, 100 Linkarooie tests, and the full Composer suite. The article adds a publication check for its own assets and links. The tests include malformed provider responses, a foreign origin, a changed limit, a hidden book, a configuration advance, mounted and standalone links, and the full open, closed, paused and full public states.

Run the same thing locally

The demonstration uses the actual app source and Wrangler's local Workers runtime. It does not need a Cloudflare account, an Access session, a paid service or a hosted deployment. It needs Node 22, the declared pnpm version, the pinned dependencies, a native Wrangler install, and a host browser for the captures.

Docker was not used for this run. The supported local path is Wrangler's workerd with local D1 and R2 emulation; a container would still download a platform-specific runtime and would still need a host browser for screenshots. That boundary is stated rather than hidden. Docker support for this walkthrough remains unexercised work.

  1. Start two disposable Workers. labs/building/start-local.sh copies the two app repositories to one owned temporary root, prepares local migrations, writes fictional local configuration and starts Guestbook on 8788 and Linkarooie on 8787. It leaves the working repositories and their owner state untouched.
  2. Apply the Init spec. seed-local.mjs creates a fresh Guestbook resource, stages and activates the Guestbook settings, applies the Linkarooie profile and links, and links the exact source for contributions. It talks only to the asserted localhost Workers.
  3. Exercise the real journey. capture-journey.mjs opens the profile, submits a fictional postcard through the real form, records the pending receipt, approves it through the app-owned moderation contract, features it, hides it at source, closes and reopens the Guestbook, and captures phone and desktop states. Every assertion is written to output/build-199/captures/report.json.
  4. Stop and remove the owned run. stop-local.sh stops only the two processes it started and removes the temporary root. The working repositories, their .wrangler state, owner records and browser sessions are not touched.
Commands used in this run
# From the Composer repository
labs/building/start-local.sh
RESOURCE_ID=build199-trail-8   INIT_SPEC=public/downloads/building-199-init-spec.json   node labs/building/seed-local.mjs
RESOURCE_ID=build199-trail-8   PLAYWRIGHT_MODULE=/path/to/playwright/index.mjs   node labs/building/capture-journey.mjs
labs/building/stop-local.sh

The exact local addresses, source revisions, screenshot hashes and capture note are in the capture provenance. The run is not a deployment: it proves the app contracts, states and browser behaviour under local emulation, not a Cloudflare release or a live owner configuration.

The journey, in the order a person sees it

1. The visitor writes a note

Guestbook postcard form with a fictional message, alias and consent checked, and the guest code field left blank for the capture.
The Guestbook-owned form. The owner shares the guest code separately; the code is omitted from this capture. The message and alias are the visitor's own words.
Guestbook postcard form after submission with the Postcard sent button and a status saying the postcard is with the owner and stays private unless approved.
The pending receipt. The record is private; its media is not public. The button says “Postcard sent”, not “Published”.

2. The owner reviews at the source

Public Guestbook page showing the approved fictional postcard and the Leave a postcard action.
After owner approval, the postcard is public in Guestbook. The source page still owns the approved collection and its media.
Linkarooie profile with the approved postcard in the featured section, a Leave a note panel and four authored links.
After a separate featuring choice, Linkarooie shows the card and resolves its live source. The contribution action remains available.

3. Revocation and closed states are visible, not implied

Linkarooie profile after the source note was hidden, with the featured postcard gone and no stale media.
Hiding the item at source removed the card on the next request. Media and item API returned 404; the authored links and the open invitation remained.
Linkarooie profile with a Guestbook panel explaining that postcards are closed and no Leave a note button.
With the Guestbook's active configuration closed, the profile explains the state and keeps the approved-note link. No send action is invented.
Phone featured section showing two authored links and the fictional approved postcard.
The featured section at 390px.
Phone contribution form with the guest code explanation, message, alias and consent fields.
The real form at 390px. The guest code is not exposed on the profile.
Dark theme Linkarooie profile with the guestbook panel and featured postcard.
The same profile in the dark theme. The panel, source card and state logic are identical; only the colour tokens change.

The postcard image is generated by the visitor's browser from the message and alias. It is not a photograph, and the CTA does not add a photo-upload path to Linkarooie. The screenshot set is labelled by state; a pending note never appears in the featured section.

Why this is composition, not a merged app

Flow from a visitor note to the Guestbook form, pending review, owner approval, a separate Linkarooie featuring choice, a profile card, and source revocation removing both public views.Flow from a visitor note to the Guestbook form, pending review, owner approval, a separate Linkarooie featuring choice, a profile card, and source revocation removing both public views.
The source owns the record. Linkarooie owns the curation. The two public views resolve the same source state, so revocation is not a second copy to clean up.
Text version of the contribution flow
  1. A visitor opens the profile. The Guestbook form is offered only while the source says it is open.
  2. The form creates a pending private record and media in Guestbook.
  3. The owner approves, rejects or hides it in Guestbook.
  4. An approved item is public in Guestbook. Linkarooie may feature it only after a separate owner choice.
  5. Hiding or revoking it at source removes the card and media on the next request.
Who owns what
ThingOwnerWhat the other app may do
Postcard text and PNGGuestbookLinkarooie receives only a current public item projection when eligible.
Review, approval, hide, deleteGuestbookLinkarooie cannot moderate, reply or restore it.
Which item is featuredLinkarooieGuestbook does not publish a card on Linkarooie.
Workspace, stack, publicationApp ComposerThe apps keep their own records and APIs.
Invitation stateGuestbook, current active configurationLinkarooie reads it and renders the honest public state.

The contract is a request, not a copy. Linkarooie does not receive a moderation token, a guest code, the pending text or a durable copy of the media. It stores the exact source reference and a card. Every public render resolves the source again.

The shape of the system

Architecture diagram: visitors and owners reach App Composer; the agent changes reviewed source; Composer pins source and connects to independent Guestbook and Linkarooie Workers with their own D1 and R2 data; live identity is observed separately.Architecture diagram: visitors and owners reach App Composer; the agent changes reviewed source; Composer pins source and connects to independent Guestbook and Linkarooie Workers with their own D1 and R2 data; live identity is observed separately.
The local run substitutes local Wrangler services for the hosted bindings. The ownership boundaries and app code are the same; the deployment identity is not.
Text version of the architecture
  1. The visitor uses public HTTPS pages; the invited owner uses the existing Access and EmDash role boundary.
  2. The authorised agent changes reviewed source in the independent repositories. Prompt text is data, not authority.
  3. Composer pins exact app source and connects to the apps through declared service bindings.
  4. Guestbook keeps its own D1, R2 media, review and revocation. Linkarooie keeps its own D1, links and curation.
  5. Live identity is a separate check of the deployed Worker version, custom hostname and observed behaviour.

The workflow that produced it

Workflow from brief to Get Ready, Init, agent implementation, local run, review, guarded release and live verification, with a feedback loop back to implementation.Workflow from brief to Get Ready, Init, agent implementation, local run, review, guarded release and live verification, with a feedback loop back to implementation.
The dashed edge is the part that matters. A local run or a review finding sends the work back to implementation; it is not a one-way pipeline.
Text version of the workflow
  1. Start with the outcome and limits. Use Get Ready only when the environment or target is not already verified.
  2. Turn the brief into an Init specification. The spec is portable data; it grants no execution authority.
  3. The agent implements the change in the selected source, with tests and a local run.
  4. Review the rendered phone, desktop, keyboard and failure states. Send findings back to implementation.
  5. Release through the guarded path, then verify the exact source, hostname and behaviour. The next change starts a new loop.

What this proves, and what it does not

Proved in this run

  • The exact-source invitation contract works against two real local Workers and real SQLite-backed D1 behaviour.
  • A pending note is private; approval makes it public; featuring is a separate explicit card; hiding returns 404 for the item API and media and removes the card.
  • Closed, paused, full and unavailable states do not render a send action, and mounted/standalone links preserve their destinations.
  • The owner can link, replace or stop the invitation from the native collection control; a hidden or pending book is refused.
  • The article, screenshots and diagrams are built from the same repository and capture run that are linked here.

Not proved

  • No hosted deployment of this contribution feature is claimed from the local run, and the owner's live profile configuration was not changed.
  • No photo upload from the Linkarooie CTA, no public comment thread, no replies, notifications, guest accounts or general multi-tenant signup.
  • No screen-reader, physical touch-device, external social unfurl or full migration acceptance.
  • Docker was not exercised for this walkthrough; the supported local path is Wrangler's runtime.
  • Restoring code does not restore hidden or deleted data. Configuration rollback and content/media recovery remain separate.

The guest code is an existing narrow admission control, shared by the owner. It is not an account, and it never grants the owner studio. If the owner has not shared it, the form explains that before asking for a message.

Try it, or take the source

The starting point is Init for a portable composition, Get Ready when the environment is not yet proven, and Your app, your admin for the owner surface. The public fictional stacks remain available at Fictional Trail Notes and Fictional Launch Board.

Narration and shot list

A short version for a recorded walkthrough. The article is the complete reference; the list is not a separate claim.

  1. Open on the finished profile. “This is a real profile, four links, one guestbook invitation and one approved note.” Show desktop then phone.
  2. Explain the two jobs. “Guestbook owns the note and the review. Linkarooie owns the profile and the choice to feature it.”
  3. Show the brief and the Init spec. “The starting composition is portable data. It does not grant deployment authority.”
  4. Show the change. “The source now answers one question: does this exact Guestbook accept a postcard right now?”
  5. Submit the note. Show the real form, the pending receipt and the private state.
  6. Approve and feature. Show the Guestbook page, then the separate Linkarooie featuring step and card.
  7. Hide at source. Refresh the profile. “The card and media are gone; the source still owns the record.”
  8. Close the Guestbook. Refresh. “No button, no dead form. The profile explains the state.” End on the source links and the local run procedure.