Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 61 additions & 3 deletions THEME.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ what it does for you.

Allowed ops: `discover`, `zones`, `createZone`, `apps`, `appRepos`,
`shipApp`, `updateApp`, `deleteApp`, `redeploy`, `buildLogs`, `metrics`,
`quota`, `character`, `saveCharacter`.
`quota`, `character`, `saveCharacter`, `forgeSite`, `forgeSiteJob`,
`forgeSiteJobs`, `inferSite`, `forgeSiteUpdate`, `domainStatus`,
`deleteSiteRepo`.

## Streams (push, not request/response)

Expand All @@ -55,8 +57,8 @@ sees a credential.

`discover().capabilities` lists what this platform implements. Only offer a
feature when its flag is present: `runtime-logs`, `quota`, `app-events`,
`volumes`, `custom-domains` (plus the original `apps`, `zones`, `themes`,
`character`).
`volumes`, `custom-domains`, `site-forge`, `site-update`, `domain-status`
(plus the original `apps`, `zones`, `themes`, `character`).

## Newer app fields

Expand All @@ -80,6 +82,62 @@ feature when its flag is present: `runtime-logs`, `quota`, `app-events`,
Render the list as given — do not re-sort — so every theme agrees on
environment order.

## Site forge (capability: `site-forge`)

When `discover().capabilities` includes `site-forge`, the platform can
generate a complete static website from interview answers and push it to
git — the theme then ships the repo like any other app.

- `theme.forgeSite(org, {name, kind, vibe, headline, extras})` starts a job.
`kind` is `portfolio | blog | landing | docs`; `name` a lowercase RFC-1123
label. Returns the job `{id, phase, …}`.
- `theme.forgeSiteJob(org, id)` polls it. Phases: `Designing → Writing →
Pushing → Ready | Failed`. `phase_started_at` + `message` (heartbeat with
elapsed) power honest progress UI; `brief` is the design brief once
drafted. On `Ready`, `repo_url`/`repo_name` are exactly what `shipApp`
needs.
- `theme.forgeSiteJobs(org)` lists jobs newest-first (rehydration).

The job does NOT register the app: call `shipApp({repo_url, repo_name,
zone_ref, region, size, port: 8080, public_url_enabled: true})` yourself
once Ready, then follow the app's build via `apps`/`deployments`/`buildLogs`.
Without the capability flag, hide the feature entirely.

v2 additions:

- `forgeSite` requests are free-text-first: `{name, headline, prompt}` where
`prompt` is whatever the user typed (any site type). The v1 `kind` field is
accepted but no longer an enum.
- `theme.inferSite(org, {prompt})` → `{name, headline, summary}` —
synchronous guesses for a confirm screen. It can 503/fail; always keep a
local slug fallback so the flow never dead-ends.
- `theme.forgeSiteUpdate(org, {name, instruction})` (capability
`site-update`) → an update job (`type: "update"`, phases `Reading →
Writing → Pushing → Ready | Failed`) that commits the change to the site
repo's `main` WITHOUT force — user commits are never erased, and the
platform rebuilds automatically (watch `deployments` for `reason:
"COMMIT"`). One running update per site; a second gets 409. Omitted files
keep their contents — updates cannot delete files.
- `theme.domainStatus(org, name)` (capability `domain-status`) → the live
public-resolver view of the app's custom-domain wiring:
`{domain, txt_record, txt_expected, txt_ok, verified, cname_target,
resolved_target, cname_ok, serving, checked_at}`. Render a copy-paste
record checklist from it, and NEVER present the domain as a clickable link
before `serving` is true — resolver caches lie, especially the user's.
- Forged sites are identifiable by convention: `repo_url` contains
`/site-<org>-`. The convention (plus a `konduit-site` GitLab topic) is the
only marker — deleting a theme leaves the sites as plain apps.
- `discover().platform_url` is the platform frontend origin — the base for
deep links back into the platform (Konduit's app view is
`<platform_url>/theme/<org>?zone=<environment>&app=<app_name>`). Never
derive this from `document.referrer`.
- Deleting a forged site is two explicit steps: `deleteApp` removes the app,
its build and instances (the git repo survives); then, only if the user
opts in, `theme.deleteSiteRepo(org, name)` permanently deletes the
`site-<org>-<name>` repository. Repo deletion is TeamAdmin+ (a 403 for
developers — offer the app-only path) and is NOT undoable: confirm
explicitly, never bundle it silently into app deletion.

v1 themes (token-in-fragment + direct API fetch) stop working once the
platform removes theme-origin CORS: replace your copy of `theme.js` with
this repo's version — the method signatures are unchanged.
7 changes: 7 additions & 0 deletions static/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ const theme = (() => {
deleteApp: (org, name) => call("deleteApp", org, name),
redeploy: (org, name) => call("redeploy", org, name),
buildLogs: (org, name) => call("buildLogs", org, name),
forgeSite: (org, spec) => call("forgeSite", org, spec),
forgeSiteJob: (org, id) => call("forgeSiteJob", org, id),
forgeSiteJobs: (org) => call("forgeSiteJobs", org),
inferSite: (org, spec) => call("inferSite", org, spec),
forgeSiteUpdate: (org, spec) => call("forgeSiteUpdate", org, spec),
domainStatus: (org, name) => call("domainStatus", org, name),
deleteSiteRepo: (org, name) => call("deleteSiteRepo", org, name),
metrics: (org, name, opts) => call("metrics", org, name, opts),
quota: (org) => call("quota", org),
character: (org) => call("character", org),
Expand Down