create-coline-app 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,62 @@
1
+ # create-coline-app package agent guide
2
+
3
+ This package owns the external developer experience: the `create-coline-app`
4
+ scaffold and the `coline-app push/dev` CLI. The template is a tested public
5
+ contract, not illustrative pseudocode.
6
+
7
+ ## Source of truth
8
+
9
+ - `src/scaffold.ts` copies `templates/backendless` and replaces app
10
+ placeholders.
11
+ - `src/push.ts` bundles `app.config.ts`, extracts the manifest, and collects
12
+ `main.tsx` plus app `.ts`, `.tsx`, and `.css` source for React surfaces.
13
+ - `templates/backendless/AGENTS.md` is the primary guidance delivered to
14
+ every generated app.
15
+ - `templates/backendless/.agents/skills/` contains the reusable task skill
16
+ discovered by OpenCode/Codex-style agents.
17
+
18
+ ## Non-negotiable scaffold contract
19
+
20
+ After a clean `npx create-coline-app app && cd app && npm install`, the project
21
+ must have a working backend example, a working React starter, accurate agent
22
+ guidance, and tests that do not need the Coline product repository or a running
23
+ Coline server.
24
+
25
+ Keep these versions aligned in one release:
26
+
27
+ - `@colineapp/sdk` — logic, manifest, capabilities, tree UI, test workspace
28
+ - `@colineapp/ui` — React provider, hooks, components, stylesheet
29
+ - `@colineapp/app-runtime` — transitive guest/host bridge dependency
30
+ - `create-coline-app` — scaffold and push/dev CLI
31
+
32
+ When any public API changes, update the package README, package `AGENTS.md`,
33
+ template `AGENTS.md`, template skill, template dependencies, and the focused
34
+ smoke test together.
35
+
36
+ ## Agent-safety rules
37
+
38
+ - A generated app must be self-describing. Agents should not need to inspect
39
+ `/Users/radin/coline-app`, a worktree, or private first-party apps.
40
+ - Do not make the template point at local workspace packages or private URLs.
41
+ - Do not claim a dependency is “already wired” unless it is in the generated
42
+ `package.json` and exercised by the template.
43
+ - Do not add a React example without the `main.tsx` entry, React peer packages,
44
+ CSS export, and a manifest surface that references it.
45
+ - Keep external hosting, network, auth, and database claims explicit. The
46
+ default app is hosted and backendless.
47
+
48
+ ## Verification
49
+
50
+ - Run `pnpm --filter create-coline-app typecheck` and
51
+ `pnpm --filter create-coline-app test`.
52
+ - The CLI tests must assert placeholder replacement, exact package dependency
53
+ wiring, the React starter's presence, and that the app config bundles against
54
+ the real SDK source.
55
+ - Never use a checked-in private app as the only proof that a public scaffold
56
+ works. Add the smallest self-contained fixture instead.
57
+
58
+ ## Release hygiene
59
+
60
+ The package publishes `bin`, `src`, `templates`, `README.md`, `AGENTS.md`, and
61
+ `LICENSE`. Bump the CLI version when the template contract changes. Publish
62
+ the three runtime libraries first, then publish this CLI with matching ranges.
package/README.md CHANGED
@@ -9,9 +9,10 @@ cd my-app && npm install && npm test
9
9
 
10
10
  Two binaries ship from this package:
11
11
 
12
- - **`create-coline-app <dir>`** — scaffolds a backendless app: a
13
- `defineApp` config with a Kairo tool, a file type with tree surfaces,
14
- a hosted home surface, and tests against `@colineapp/sdk/testing`.
12
+ - **`create-coline-app <dir>`** — scaffolds a self-contained backendless app:
13
+ a `defineApp` config with a Kairo tool, a file type with tree surfaces, a
14
+ working React home using `@colineapp/ui`, mobile/tree fallback rendering,
15
+ agent guidance, and tests against `@colineapp/sdk/testing`.
15
16
  - **`coline-app push | dev`** — builds the logic bundle with esbuild,
16
17
  extracts the manifest through the two-artifact model (plan §6.2),
17
18
  collects client source when the manifest declares react surfaces, and
@@ -20,3 +21,8 @@ Two binaries ship from this package:
20
21
 
21
22
  The CLI never builds tier-2 client bundles itself — it ships source and
22
23
  Coline's reviewed build pipeline produces the bundle.
24
+
25
+ The generated project includes `AGENTS.md` and
26
+ `.agents/skills/coline-app-development/SKILL.md`. Those files are deliberately
27
+ specific to the installed public packages so coding agents can work without
28
+ reading Coline's private product repository.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-coline-app",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Scaffold, push, and iterate on Coline Apps.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,6 +12,7 @@
12
12
  "src",
13
13
  "templates",
14
14
  "README.md",
15
+ "AGENTS.md",
15
16
  "LICENSE"
16
17
  ],
17
18
  "dependencies": {
package/src/cli.test.ts CHANGED
@@ -48,8 +48,21 @@ describe("runCreate", () => {
48
48
  expect(config).not.toContain("__APP_KEY__");
49
49
  const packageJson = JSON.parse(await readFile(join(dir, "package.json"), "utf8")) as {
50
50
  name: string;
51
+ dependencies: Record<string, string>;
52
+ devDependencies: Record<string, string>;
51
53
  };
52
54
  expect(packageJson.name).toBe("team-crm");
55
+ expect(packageJson.dependencies["@colineapp/sdk"]).toBe("^0.3.1");
56
+ expect(packageJson.dependencies["@colineapp/ui"]).toBe("^0.3.1");
57
+ expect(packageJson.dependencies.react).toBe("^19.0.0");
58
+ expect(packageJson.dependencies["react-dom"]).toBe("^19.0.0");
59
+ expect(packageJson.devDependencies["create-coline-app"]).toBe("^2.3.0");
60
+ await expect(readFile(join(dir, "main.tsx"), "utf8")).resolves.toContain(
61
+ "ColineAppProvider",
62
+ );
63
+ await expect(
64
+ readFile(join(dir, ".agents/skills/coline-app-development/SKILL.md"), "utf8"),
65
+ ).resolves.toContain("Do not inspect");
53
66
  await expect(readFile(join(dir, ".gitignore"), "utf8")).resolves.toContain("node_modules");
54
67
  });
55
68
 
@@ -97,4 +110,39 @@ describe("runCreate", () => {
97
110
  expect(imported.default.manifest.key).toBe("smoke-app");
98
111
  expect(imported.default.manifest.tools[0]?.name).toBe("smoke-app.create_note");
99
112
  });
113
+
114
+ it("contains a React starter that bundles against the public UI packages", async () => {
115
+ const dir = await scaffoldTemp("react-smoke");
116
+ const packagesRoot = resolve(__dirname, "..", "..");
117
+ const sdkRoot = join(packagesRoot, "sdk");
118
+ const runtimeRoot = join(packagesRoot, "app-runtime");
119
+ const uiRoot = join(packagesRoot, "coline-ui");
120
+ const sdkRequire = createRequire(join(sdkRoot, "package.json"));
121
+ const uiRequire = createRequire(join(uiRoot, "package.json"));
122
+ const result = await build({
123
+ entryPoints: [join(dir, "main.tsx")],
124
+ bundle: true,
125
+ write: false,
126
+ format: "iife",
127
+ platform: "browser",
128
+ target: "es2022",
129
+ jsx: "automatic",
130
+ logLevel: "silent",
131
+ absWorkingDir: packagesRoot,
132
+ nodePaths: [resolve(packagesRoot, "..", "node_modules")],
133
+ alias: {
134
+ "@colineapp/sdk/v2": join(sdkRoot, "src", "v2.ts"),
135
+ "@colineapp/app-runtime": join(runtimeRoot, "src", "index.ts"),
136
+ "@colineapp/ui": join(uiRoot, "src", "index.ts"),
137
+ "@colineapp/ui/styles.css": join(uiRoot, "src", "styles", "base.css"),
138
+ react: uiRequire.resolve("react"),
139
+ "react-dom": uiRequire.resolve("react-dom"),
140
+ "react-dom/client": uiRequire.resolve("react-dom/client"),
141
+ "react/jsx-runtime": uiRequire.resolve("react/jsx-runtime"),
142
+ "zod/v4": sdkRequire.resolve("zod/v4"),
143
+ },
144
+ loader: { ".css": "empty" },
145
+ });
146
+ expect(result.outputFiles?.[0]?.text).toBeTruthy();
147
+ });
100
148
  });
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: coline-app-development
3
+ description: Build and debug a Coline App using the public SDK, hosted capabilities, tree UI, and sandboxed React UI.
4
+ ---
5
+
6
+ # Coline App development
7
+
8
+ Use this skill for any task that changes a generated Coline App.
9
+
10
+ ## Start from the shipped contract
11
+
12
+ Read the repository `AGENTS.md`, `app.config.ts`, `main.tsx`, `app.test.ts`,
13
+ and `package.json`. The generated project is intentionally self-contained.
14
+ Do not inspect `/Users/radin/coline-app`, private worktrees, first-party apps,
15
+ or product internals to learn an app-authoring API. Use the installed package
16
+ types, README files, and this skill.
17
+
18
+ The project has two halves:
19
+
20
+ - `app.config.ts` is the hosted logic bundle: manifest, permissions, tools,
21
+ file types, tree handlers, and capability calls.
22
+ - `main.tsx` is the React client entry. The starter includes `@colineapp/ui`,
23
+ React, ReactDOM, and the CSS export; do not add a second bridge or fetch
24
+ data from a Coline API route.
25
+
26
+ ## Choose the right surface
27
+
28
+ - Use `ui.*` tree nodes for previews, inline surfaces, mobile fallbacks, and
29
+ simple Kairo cards.
30
+ - Use a React surface when the app needs local interaction, filtering, forms,
31
+ or an editor. Declare `{ tier: "react", entry: "main.tsx" }` and keep the
32
+ root wrapped in `<ColineAppProvider>`.
33
+ - Use `useColine()` for capability calls and `useColineQuery()` for a bounded
34
+ loading/error/refetch loop. Never use `fetch`, localStorage, cookies, or
35
+ credentials in the sandbox.
36
+
37
+ ## Public SDK rules
38
+
39
+ - Import logic from `@colineapp/sdk/v2` and tests from `@colineapp/sdk/testing`.
40
+ - Prefix every tool name with the app key.
41
+ - Validate inputs with `zod/v4` and declare honest effects (`read`, `write`,
42
+ `destructive`, or `external`).
43
+ - Ask for the minimum permissions. Capability calls still require both the
44
+ declared permission and the tool effect ceiling.
45
+ - Collections are typed record envelopes. `where` is exact-match on top-level
46
+ fields; query bounded pages and filter more complex predicates in memory.
47
+ - Use `ui.*` for hosted handlers. Chat tool cards must stay within the
48
+ validated chat subset and remain small.
49
+
50
+ ## UI rules
51
+
52
+ - Import components from `@colineapp/ui` and `@colineapp/ui/styles.css`.
53
+ - Use the host theme tokens (`bg-background`, `text-foreground`,
54
+ `text-muted-foreground`, and related tokens); dark mode is supplied by the
55
+ host.
56
+ - Render explicit loading, empty, and error states.
57
+ - Keep components accessible and use the provided controls before writing
58
+ custom primitives.
59
+ - Do not add a state library or network client for the first implementation.
60
+
61
+ ## Verify before pushing
62
+
63
+ ```sh
64
+ npm run typecheck
65
+ npm test
66
+ ```
67
+
68
+ Then push with `npx coline-app dev --internal` or `npx coline-app push --internal`
69
+ when the user has supplied `COLINE_BASE_URL` and `COLINE_API_KEY`. The local
70
+ test workspace is the first proof; the hosted app is the second.
@@ -1,196 +1,169 @@
1
1
  # AGENTS.md — building __APP_NAME__ (a Coline App)
2
2
 
3
- You are working on a Coline App. Everything an agent needs is in this file
4
- no URLs required. The scaffold compiles, tests, and runs as-is; evolve it
5
- rather than starting from a blank page.
3
+ This is a self-contained Coline App project. Read this file before changing
4
+ code. The public SDK, UI package, tests, and this guide are the contract; do
5
+ not inspect the Coline product repository or private worktrees to discover how
6
+ to build an app.
6
7
 
7
- ## What a Coline App is
8
+ ## Project model
8
9
 
9
- A Coline App runs INSIDE a Coline workspace on Coline's hosted runtime.
10
- There are no servers, no database, no auth, no deploys — the platform
11
- provides all of it through the `coline.*` capability API:
10
+ Coline hosts the app logic. There is no server, database, auth flow, or deploy
11
+ step in this project:
12
12
 
13
- - `app.config.ts` the ENTIRE backend: manifest + tools + file types +
14
- UI handlers, bundled and executed server-side by Coline.
15
- - `main.tsx` (optional) a React UI running in a locked sandbox iframe.
16
- Create it only if the manifest declares a `react` tier surface.
17
- - `app.test.ts` tests against an in-memory fake workspace.
13
+ - `app.config.ts` is the hosted logic bundle: manifest, permissions, tools,
14
+ file types, tree handlers, and capability calls.
15
+ - `main.tsx` is the React client entry for the starter home surface. It runs in
16
+ a locked sandbox iframe and uses `@colineapp/ui`.
17
+ - `app.test.ts` runs the logic against `createTestWorkspace`, an in-memory
18
+ capability fake. It does not need a Coline server.
19
+ - `.agents/skills/coline-app-development/SKILL.md` is the detailed task
20
+ workflow for agents that support skills.
18
21
 
19
22
  ## Commands
20
23
 
21
- - `npm test` — run tests (in-memory workspace; no Coline instance needed)
22
- - `npm run typecheck` — strict TS; keep it clean
23
- - `npx coline-app dev --internal` — watch mode; every save pushes a
24
- `0.0.0-dev` version that hot-swaps the live app (reload the app page)
25
- - `npx coline-app push --internal` — one-off push as a workspace-internal
26
- app (auto-approved, private to the workspace)
27
- - Needs `COLINE_API_KEY` (workspace API key with `apps.write` scope) and
28
- `COLINE_BASE_URL` (the Coline instance) in the environment.
24
+ ```sh
25
+ npm install
26
+ npm run typecheck
27
+ npm test
29
28
 
30
- ## The manifest (`defineApp`)
31
-
32
- ```ts
33
- export default defineApp({
34
- key: "__APP_KEY__", // immutable, lowercase, hyphens
35
- name: "__APP_NAME__",
36
- description: "…",
37
- permissions: [...], // ONLY what you use — users see this list
38
- hosting: { default: "coline" },// hosted runtime (no external server)
39
- surfaces: { home: { tier: "tree" } }, // or { tier: "react" } with main.tsx
40
- files: [...], // custom file types (optional)
41
- tools: [...], // Kairo tools (optional)
42
- handlers: { renderHome: async (context) => uiNode },
43
- });
29
+ # after local checks pass and the user has supplied these variables:
30
+ npx coline-app dev --internal
31
+ npx coline-app push --internal
44
32
  ```
45
33
 
46
- ### Permissions (request the minimum)
47
-
48
- | key | grants |
49
- | --- | --- |
50
- | `storage.app` | app key-value + record collections |
51
- | `files.read` / `files.write` | read / create workspace files |
52
- | `drives.app` | a private drive owned by the app |
53
- | `members.read` | list workspace members |
54
- | `search.index` / `search.query` | add to / query workspace search |
55
- | `notifications.write` | send notifications |
56
- | `ai.generate` | call Coline's AI model (200/day) |
57
- | `ai.tools` | expose tools to Kairo (the workspace AI) |
58
- | `events.emit` | publish app events |
59
- | `commands.register` | add command-palette commands |
60
- | `realtime.subscribe` | live updates in the UI |
61
- | `network.external` | HTTPS fetch to allowlisted hosts only |
62
-
63
- ## Tools (`defineTool`) — how Kairo operates your app
34
+ `COLINE_BASE_URL` selects the Coline instance and `COLINE_API_KEY` must be a
35
+ workspace API key with `apps.write`. The CLI uploads `app.config.ts` as a
36
+ logic bundle and submits `main.tsx` plus app `.ts`, `.tsx`, and `.css` source
37
+ when the manifest declares a React surface. It never uploads `node_modules`.
64
38
 
65
- ```ts
66
- const logDecision = defineTool({
67
- name: "__APP_KEY__.log_decision", // MUST be prefixed with the app key
68
- description: "…", // what Kairo reads to pick the tool
69
- input: z.object({ title: z.string().min(1) }), // zod schema
70
- effect: "write", // read | write | destructive | external
71
- execute: async (input, context) => {
72
- // context.coline — the capability client
73
- // context.workspace — { id, slug, name }
74
- // context.actor — who invoked it
75
- return {
76
- output: { id: "…" }, // structured result for the model
77
- card: ui.stack([...]), // optional rich card shown in chat
78
- };
79
- },
80
- });
81
- ```
39
+ ## Public package contract
82
40
 
83
- `effect` is ENFORCED by the runtime, not a hint: a `read` tool cannot
84
- write. Reads auto-approve; `write` follows the session's permission mode;
85
- `destructive`/`external` always prompt the user. Be honest.
86
-
87
- ## Storage — `coline.storage`
41
+ Use only these app-facing entry points:
88
42
 
89
43
  ```ts
90
- // Key-value
91
- await context.coline.storage.kv.set("settings", { theme: "auto" });
92
- const settings = await context.coline.storage.kv.get("settings");
44
+ import { z } from "zod/v4";
45
+ import { defineApp, defineFileType, defineTool, ui } from "@colineapp/sdk/v2";
46
+ import { createTestWorkspace } from "@colineapp/sdk/testing";
47
+ ```
93
48
 
94
- // Typed record collections (the main data store)
95
- interface Decision { title: string; status: "proposed" | "decided"; tags: string[] }
96
- const decisions = context.coline.storage.collection<Decision>("decisions");
49
+ For React:
97
50
 
98
- const rec = await decisions.insert({ title: "Use Postgres", status: "decided", tags: ["infra"] });
99
- await decisions.update(rec.id, { status: "proposed" });
100
- await decisions.get(rec.id); // envelope | null
101
- await decisions.delete(rec.id);
102
- const { records, nextCursor } = await decisions.query({
103
- where: { status: "decided" }, // exact-match on top-level fields
104
- orderBy: "title", order: "asc", // or omit for newest-first
105
- limit: 50, cursor: undefined,
106
- });
107
- // Every record comes wrapped: { id, createdBy, createdAt, updatedAt, data }
51
+ ```tsx
52
+ import { createRoot } from "react-dom/client";
53
+ import { ColineAppProvider, useColine, useColineQuery } from "@colineapp/ui";
54
+ import "@colineapp/ui/styles.css";
108
55
  ```
109
56
 
110
- Limits: 5,000 kv keys · 200,000 records per collection · 200 kB per value.
111
- `query.where` is exact-match only filter/search in memory after querying
112
- when you need contains/ranges (keep result sets bounded with `limit`).
57
+ The scaffold installs the SDK, UI, React, ReactDOM, and their type packages.
58
+ Do not replace them with private workspace paths or a second bridge.
113
59
 
114
- ## UI two tiers
60
+ ## Manifest and permissions
115
61
 
116
- ### Tree tier (`ui.*` builders) — renders everywhere incl. mobile & chat cards
62
+ Keep permissions minimal. Common permissions include:
117
63
 
118
- `ui.stack(children, { direction?, gap? })` · `ui.row(children)` ·
119
- `ui.heading(text, { level? 1-4 })` · `ui.text(text, { tone? })` ·
120
- `ui.badge(text, { tone? })` · `ui.card({ title?, description?, children?, footer? })` ·
121
- `ui.button({ label, action, variant? })` · `ui.divider()` ·
122
- `ui.emptyState({ title, description? })` · `ui.fileCard({ title, fileId, action? })` ·
123
- `ui.table({ columns, rows })` · `ui.link({ label, href })` · `ui.image({ src, alt })`
64
+ | Permission | Use |
65
+ | --- | --- |
66
+ | `storage.app` | App-private key/value and typed record collections |
67
+ | `files.read` / `files.write` | Read or create workspace files |
68
+ | `ai.tools` | Expose app tools to Kairo |
69
+ | `members.read` | Read workspace member summaries |
70
+ | `search.index` / `search.query` | Index or query workspace search |
71
+ | `network.external` | Use `coline.net.fetch` for allowlisted hosts |
124
72
 
125
- Tones: `default | muted | positive | warning | danger`.
126
- Actions: `actions.openFile(fileId)`, `actions.openAppHome()`,
127
- `actions.navigate(path)`, `actions.createFile(...)`, `actions.custom(name, payload)`.
128
- Chat cards (tool results) allow a validated subset — keep them simple
129
- (stack/heading/text/badge/table/fileCard are all safe). Max 120 nodes.
73
+ Tool effects are enforced ceilings, not labels:
130
74
 
131
- ### React tier (`main.tsx` + `@colineapp/ui`) — full React in a sandbox
75
+ - `read` for reads only
76
+ - `write` for ordinary mutations
77
+ - `destructive` for irreversible or deletion-like actions
78
+ - `external` for outbound side effects
132
79
 
133
- Set `surfaces: { home: { tier: "react" } }` and create `main.tsx`:
80
+ Every tool input is a Zod schema and every tool name is prefixed with the app
81
+ key (`__APP_KEY__.operation`).
134
82
 
135
- ```tsx
136
- import { createRoot } from "react-dom/client";
137
- import { ColineAppProvider, useColine, useColineQuery, useColineContext } from "@colineapp/ui";
83
+ ## Capability patterns
138
84
 
139
- function Home() {
140
- const coline = useColine(); // capability client
141
- const { data, isLoading, error, refetch } = // simple async hook
142
- useColineQuery(() => coline.storage.collection("decisions").query({ limit: 100 }), []);
143
- // …render
85
+ ```ts
86
+ interface Decision {
87
+ title: string;
88
+ status: "proposed" | "decided";
144
89
  }
145
90
 
146
- const root = document.getElementById("root");
147
- if (root) createRoot(root).render(<ColineAppProvider><Home /></ColineAppProvider>);
91
+ const decisions = context.coline.storage.collection<Decision>("decisions");
92
+ const created = await decisions.insert({ title: "Use Postgres", status: "decided" });
93
+ const page = await decisions.query({
94
+ where: { status: "decided" },
95
+ orderBy: "title",
96
+ order: "asc",
97
+ limit: 50,
98
+ });
148
99
  ```
149
100
 
150
- Sandbox rules (enforced, not conventions):
151
- - NO network (`fetch` is blocked by CSP), no localStorage, no cookies.
152
- ALL data flows through the `coline.*` client over the capability bridge.
153
- - Theme tokens follow the host automatically (dark mode included) — use
154
- the CSS variables / `@colineapp/ui` components, never hardcoded colors.
155
- - One bundle per app: switch on `useColineContext().surface` if you have
156
- multiple react surfaces.
101
+ Collection `where` is exact-match on top-level fields. Records are envelopes
102
+ with the user data under `.data`. Keep pages bounded and filter contains,
103
+ search, or range conditions in app code.
157
104
 
158
- ## Rate limits & budgets
105
+ File capabilities include `list`, `get`, `create`, `update`, `trash`,
106
+ `getDocument`, and `updateDocument`. Navigation includes `openFile`,
107
+ `openAppHome`, `navigate`, and `openReference`.
159
108
 
160
- 120 capability calls burst / 20 per second per install · `net.fetch`
161
- 20 burst / 2 per second · `ai.generate` 200/day · 10,000 executions/day.
162
- Exceeding any returns a named error visible in the execution log.
109
+ ## UI tiers
163
110
 
164
- ## Testing (`app.test.ts` pattern)
111
+ Tree UI is native and works in previews, inline surfaces, mobile, and Kairo
112
+ cards:
165
113
 
166
114
  ```ts
167
- import { describe, expect, it } from "vitest";
168
- import { createTestWorkspace } from "@colineapp/sdk/testing";
169
- import app from "./app.config";
170
-
171
- it("logs a decision", async () => {
172
- const workspace = createTestWorkspace(app);
173
- const result = await workspace.invokeTool("__APP_KEY__.log_decision", {
174
- title: "Use Postgres",
175
- });
176
- expect(result.card).not.toBeNull(); // result: { output, card }
177
- // Also available: workspace.renderHome(), workspace.files.byType(...),
178
- // workspace.renderFileSurface(...), workspace.fireSchedule(...)
179
- });
115
+ return ui.stack([
116
+ ui.heading("Decisions", { level: 1 }),
117
+ ui.text("No decisions yet.", { tone: "muted" }),
118
+ ]);
180
119
  ```
181
120
 
182
- ## Debugging
121
+ The starter home is React with a tree fallback for mobile:
183
122
 
184
- - Tool throws → the error lands in the app's execution log (developer
185
- console → your app → Executions) with the message redacted of secrets.
186
- - Permission denied → the manifest doesn't declare it, the user didn't
187
- grant it, or the tool's `effect` is too low for the operation.
188
- - UI blank → check the browser console for the sandbox frame; the
189
- capability bridge logs denials with reason codes.
123
+ ```ts
124
+ surfaces: {
125
+ home: {
126
+ tier: "react",
127
+ entry: "main.tsx",
128
+ mobile: { tier: "tree" },
129
+ },
130
+ }
131
+ ```
132
+
133
+ In `main.tsx`, put the app under `ColineAppProvider`. Call capabilities only
134
+ through `useColine()`, and use `useColineQuery(loader, deps)` for bounded
135
+ loading/error/refetch state. The host supplies theme tokens and dark mode.
136
+ Use classes such as `bg-background`, `text-foreground`,
137
+ `text-muted-foreground`, and the exported UI components.
138
+
139
+ Sandbox restrictions are real: no `fetch`, localStorage, cookies, credentials,
140
+ or direct Coline API routes. External access goes through `coline.net.fetch`
141
+ and a manifest allowlist.
190
142
 
191
- ## Style rules for the UI
143
+ ## Testing workflow
144
+
145
+ ```ts
146
+ const workspace = createTestWorkspace(app);
147
+ const result = await workspace.invokeTool("__APP_KEY__.create_note", {
148
+ title: "Hello",
149
+ });
150
+ expect(result.card).not.toBeNull();
151
+ expect(workspace.files.byType("__APP_KEY__.note")).toHaveLength(1);
152
+ const tree = await workspace.renderHome();
153
+ ```
192
154
 
193
- - No eyebrow labels / kicker pills above headings. No monospace accent
194
- fonts. Solid font weights (500–600).
195
- - Explicit loading, empty, and error states for every async view.
196
- - Dark mode must work — it's automatic if you use theme tokens.
155
+ Test tools and tree handlers without a server. React client behavior is
156
+ covered by the typecheck and the hosted dev loop after the logic tests pass.
157
+ Keep explicit loading, empty, and error states in every async React view.
158
+
159
+ ## Agent boundaries
160
+
161
+ - Do not read `/Users/radin/coline-app`, any `*.worktrees` directory, or
162
+ first-party app code to understand this public API.
163
+ - Do not add dependencies until checking whether the installed SDK/UI already
164
+ provides the capability or component.
165
+ - Do not invent signatures from a README snippet: use the installed package
166
+ declarations when a detail is unclear, then update this guide if the public
167
+ contract is missing.
168
+ - Keep changes inside this generated app. Ask the user before changing the
169
+ platform or publishing packages.
@@ -2,4 +2,5 @@
2
2
 
3
3
  Read and follow [`AGENTS.md`](./AGENTS.md) — it contains the complete
4
4
  Coline App contract: capabilities, tools, storage, UI builders, sandbox
5
- rules, limits, and testing patterns.
5
+ rules, limits, and testing patterns. For app-authoring tasks, also read
6
+ [`.agents/skills/coline-app-development/SKILL.md`](.agents/skills/coline-app-development/SKILL.md).
@@ -1,13 +1,15 @@
1
1
  # __APP_NAME__
2
2
 
3
- A backendless Coline App. The logic in `app.config.ts` runs on Coline's
4
- hosted runtime — no servers to deploy.
3
+ A hosted, backendless Coline App. The logic in `app.config.ts` runs on
4
+ Coline's hosted runtime — no servers to deploy. The starter also includes a
5
+ working React home in `main.tsx` using `@colineapp/ui`.
5
6
 
6
7
  ## Develop
7
8
 
8
9
  ```sh
9
10
  npm install
10
11
  npm test # runs against the in-memory test workspace
12
+ npm run typecheck # checks app.config.ts and main.tsx
11
13
  ```
12
14
 
13
15
  ## Push to Coline
@@ -30,8 +32,8 @@ Versions land in your developer console in draft state. Use
30
32
  it as a capability ceiling.
31
33
  - Add file types with `defineFileType` — tree previews render everywhere
32
34
  Coline shows files, including mobile and Kairo chat.
33
- - Add a React editor by setting a file surface to
34
- `{ tier: "react", entry: "main.tsx" }` and creating `main.tsx`.
35
+ - The starter home is already a React surface. Keep `main.tsx` as the single
36
+ client entry and use `@colineapp/ui` for components, provider, and hooks.
35
37
  - Store app-private data with `coline.storage.kv` and
36
38
  `coline.storage.collection(name)`.
37
39
  - Call external APIs with `coline.net.fetch` after declaring
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod/v4";
2
2
  import { actions, defineApp, defineFileType, defineTool, ui } from "@colineapp/sdk/v2";
3
3
 
4
- // __APP_NAME__ — a backendless Coline App. Everything here runs on
4
+ // __APP_NAME__ — a hosted, backendless Coline App. Everything here runs on
5
5
  // Coline's hosted runtime: no servers, no deploys. `coline-app push`
6
6
  // uploads this file as the logic bundle and main.tsx as client source.
7
7
 
@@ -60,12 +60,16 @@ export default defineApp({
60
60
  permissions: ["files.read", "files.write", "storage.app", "ai.tools"],
61
61
  hosting: { default: "coline" },
62
62
  surfaces: {
63
- home: { tier: "tree" },
63
+ home: {
64
+ tier: "react",
65
+ entry: "main.tsx",
66
+ mobile: { tier: "tree" },
67
+ },
64
68
  },
65
69
  files: [noteFileType],
66
70
  tools: [createNote],
67
71
  handlers: {
68
- // The starter home screen replace this with your app's real UI.
72
+ // The mobile/tree fallback. The React home lives in main.tsx.
69
73
  renderHome: async (context) => {
70
74
  const { files } = await context.coline.files.list({
71
75
  typeKey: "__APP_KEY__.note",
@@ -0,0 +1,100 @@
1
+ import { createRoot } from "react-dom/client";
2
+ import {
3
+ Button,
4
+ Card,
5
+ CardContent,
6
+ CardDescription,
7
+ CardHeader,
8
+ CardTitle,
9
+ ColineAppProvider,
10
+ Skeleton,
11
+ useColine,
12
+ useColineQuery,
13
+ } from "@colineapp/ui";
14
+ import type { ColineFileSummaryV2 } from "@colineapp/sdk/v2";
15
+ import "@colineapp/ui/styles.css";
16
+
17
+ function StarterHome() {
18
+ const coline = useColine();
19
+ const files = useColineQuery(
20
+ (capabilities) =>
21
+ capabilities.files.list({ typeKey: "__APP_KEY__.note", limit: 50 }),
22
+ [],
23
+ );
24
+ const notes = files.data?.files ?? [];
25
+
26
+ async function createNote(): Promise<void> {
27
+ const file = await coline.files.create({
28
+ typeKey: "__APP_KEY__.note",
29
+ name: "Untitled note",
30
+ document: { title: "Untitled note", body: "" },
31
+ });
32
+ await coline.navigation.openFile(file.fileId);
33
+ }
34
+
35
+ return (
36
+ <main className="mx-auto flex min-h-screen w-full max-w-3xl flex-col gap-6 bg-background p-6 text-foreground">
37
+ <header className="flex items-center justify-between gap-4">
38
+ <div>
39
+ <h1 className="text-xl font-semibold">__APP_NAME__ is running</h1>
40
+ <p className="mt-1 text-sm text-muted-foreground">
41
+ Edit app.config.ts or main.tsx and save to hot-swap this app.
42
+ </p>
43
+ </div>
44
+ <Button onClick={() => void createNote()}>New note</Button>
45
+ </header>
46
+
47
+ {files.isLoading ? (
48
+ <div className="flex flex-col gap-2">
49
+ <Skeleton className="h-16 w-full" />
50
+ <Skeleton className="h-16 w-full" />
51
+ </div>
52
+ ) : files.error ? (
53
+ <Card>
54
+ <CardHeader>
55
+ <CardTitle>Couldn&apos;t load notes</CardTitle>
56
+ <CardDescription>{files.error}</CardDescription>
57
+ </CardHeader>
58
+ <CardContent>
59
+ <Button variant="outline" onClick={files.refetch}>
60
+ Retry
61
+ </Button>
62
+ </CardContent>
63
+ </Card>
64
+ ) : notes.length === 0 ? (
65
+ <Card>
66
+ <CardHeader>
67
+ <CardTitle>Edit me</CardTitle>
68
+ <CardDescription>
69
+ This is a real React surface. Replace this starter with your app,
70
+ then ask Kairo to create a note called Hello.
71
+ </CardDescription>
72
+ </CardHeader>
73
+ </Card>
74
+ ) : (
75
+ <div className="flex flex-col gap-2">
76
+ {notes.map((file: ColineFileSummaryV2) => (
77
+ <button
78
+ key={file.fileId}
79
+ type="button"
80
+ onClick={() => void coline.navigation.openFile(file.fileId)}
81
+ className="rounded-xl border border-border bg-card px-4 py-3 text-left hover:bg-muted/50"
82
+ >
83
+ <p className="text-sm font-medium">{file.name}</p>
84
+ <p className="mt-1 text-xs text-muted-foreground">Open note</p>
85
+ </button>
86
+ ))}
87
+ </div>
88
+ )}
89
+ </main>
90
+ );
91
+ }
92
+
93
+ const root = document.getElementById("root");
94
+ if (root) {
95
+ createRoot(root).render(
96
+ <ColineAppProvider>
97
+ <StarterHome />
98
+ </ColineAppProvider>,
99
+ );
100
+ }
@@ -10,12 +10,17 @@
10
10
  "dev": "coline-app dev"
11
11
  },
12
12
  "dependencies": {
13
- "@colineapp/sdk": "^0.3.0",
14
- "zod": "^4.0.0"
13
+ "@colineapp/sdk": "^0.3.1",
14
+ "@colineapp/ui": "^0.3.1",
15
+ "react": "^19.0.0",
16
+ "react-dom": "^19.0.0",
17
+ "zod": "^4.3.6"
15
18
  },
16
19
  "devDependencies": {
17
- "create-coline-app": "^2.0.0",
18
- "typescript": "^5.7.2",
19
- "vitest": "^3.0.5"
20
+ "@types/react": "^19.0.0",
21
+ "@types/react-dom": "^19.0.0",
22
+ "create-coline-app": "^2.3.0",
23
+ "typescript": "^5.8.2",
24
+ "vitest": "^3.2.4"
20
25
  }
21
26
  }