@agentshouse/skills 0.1.0-ce8a631ec1d4b → 0.1.0-cf4fc47c6da39

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.
Files changed (47) hide show
  1. package/agent-instructions.json +1 -1
  2. package/guide/apps/build.md +2 -1
  3. package/guide/apps/chat.md +14 -12
  4. package/guide/apps/content-safety.md +3 -1
  5. package/guide/apps/data.md +1 -1
  6. package/guide/apps/design-functions.md +10 -0
  7. package/guide/apps/design-load.md +14 -0
  8. package/guide/apps/design-look.md +11 -0
  9. package/guide/apps/design-schema.md +13 -0
  10. package/guide/apps/design.md +12 -0
  11. package/guide/apps/example-tasks-2.md +21 -21
  12. package/guide/apps/example-tasks-3.md +2 -2
  13. package/guide/apps/example-tasks.md +6 -6
  14. package/guide/apps/functions.md +7 -7
  15. package/guide/apps/local-first.md +6 -6
  16. package/guide/apps/local-query.md +2 -2
  17. package/guide/apps/page.md +2 -3
  18. package/guide/apps/query-house.md +48 -0
  19. package/guide/apps/runtime-api.md +6 -4
  20. package/guide/apps/runtime-files.md +20 -20
  21. package/guide/apps/runtime.md +1 -0
  22. package/guide/apps/search.md +5 -4
  23. package/guide/apps/start.md +2 -0
  24. package/guide/get-started.md +2 -4
  25. package/guide/index.md +2 -2
  26. package/guide/reference/access-level.md +1 -1
  27. package/guide/reference/agent-function.md +1 -1
  28. package/guide/reference/app-manifest.md +1 -1
  29. package/guide/reference/schema.md +2 -2
  30. package/guide/sites/build.md +7 -5
  31. package/guide/sites/counter.md +32 -0
  32. package/guide/sites/entry-data-contract.md +11 -2
  33. package/guide/sites/first-load.md +15 -0
  34. package/guide/sites/functions.md +13 -0
  35. package/guide/sites/payments-stripe-2.md +78 -0
  36. package/guide/sites/payments-stripe-3.md +68 -0
  37. package/guide/sites/payments-stripe-4.md +96 -0
  38. package/guide/sites/payments-stripe.md +63 -0
  39. package/guide/sites/payments.md +13 -0
  40. package/guide/sites/runtime.md +12 -2
  41. package/guide/sites/tags.md +9 -0
  42. package/guide/skills/build-app.md +18 -7
  43. package/guide/skills/house-onboarding.md +2 -2
  44. package/guide/skills/prototype-app.md +14 -25
  45. package/guide/skills/wizard.md +1 -1
  46. package/package.json +1 -1
  47. package/upstream.json +2 -2
@@ -4,7 +4,7 @@
4
4
  "Work with House through the `house` CLI, `house <tool> '<arguments as JSON>'`: `house --help` lists the Tools and `house help <tool>` names a Tool's arguments."
5
5
  ],
6
6
  "routing": [
7
- "Read with `shell` and `inspect`. Write with `files_batch` and `records_append`.",
7
+ "Read with `shell` and `info`. Write with `files_batch`; `records_append` adds a record from one of your Sources.",
8
8
  "For anything else, `find_command`, then `run_command`.",
9
9
  "Optional per-Room instructions: `/rooms/private/rooms/<handle>/AGENTS.md`."
10
10
  ],
@@ -2,8 +2,9 @@
2
2
 
3
3
  How to build an App over your User's Rooms and publish it, one page per step.
4
4
 
5
- An App is a web page over your User's Rooms, for your User and whom they share it with (`shares/create`). House serves the built files and gives the page the App runtime, which holds every bound Room in the browser and syncs it with House. There is no server, backend or House API of yours to call.
5
+ An App is a web page over your User's Rooms, for your User and whom they share it with (`shares/create`). House serves the built files and gives the page the App runtime, which holds every bound Room but a Search-level one in the browser and syncs it with House. There is no server, backend or House API of yours to call.
6
6
 
7
+ - [Design an App](apps/design)
7
8
  - [Start an App](apps/start)
8
9
  - [An App's data](apps/data)
9
10
  - [Local first](apps/local-first)
@@ -9,9 +9,10 @@ object House hands the page as `window.houseBridge`, which `houseBridge()` of
9
9
  `@agentshouse/app-bridge` returns, draws it in `element` over the App's own client and
10
10
  resolves to a handle: `chat.update(options)` replaces its options and `chat.unmount()`
11
11
  removes it. It fills the element and lays itself out by the width it gives: the list of the
12
- User's Agents and their chats or one chat on a narrow element, the list beside the chat on a
13
- wider one once the User shows it or the element passes 1000 px, and the chat's details beside
14
- both on a wide one. With no chat open it shows what
12
+ User's Agents and their chats or one chat on a narrow element, and on a wider one the list
13
+ beside the chat while the User shows it by its toggle, closed at first beside an open chat and
14
+ open where none is, the conversation keeping its width where the element has room for both.
15
+ With no chat open it shows what
15
16
  asks the User and what works now. `conversation` opens one chat, and with it `null` the Chat
16
17
  opens a new chat with `agent`, the Agent it goes to, or the list with neither. `onOpen` hears
17
18
  each chat it opens as `{ conversation, agent }`, and both `null` when the User goes back to the
@@ -20,17 +21,17 @@ Or mount it as the Chat button, which takes no element:
20
21
  `const button = await houseBridge.chat.mountButton(options)` draws House's dot at the
21
22
  window's bottom-right corner, which counts what asks the User or, while nothing does, the chats
22
23
  that replied since the User opened them, and breathes while a turn runs; pressing it opens the
23
- Chat in a sheet above the page, and pressing it again hides it. With `agent` among the options
24
+ Chat in a sheet above the page, and a second press hides it. With `agent` among the options
24
25
  the dot's first press opens it on a new chat with that Agent instead of a chat kept from an
25
- earlier visit, and later presses reopen the chat it last showed; the sheet stays hidden until
26
- the first press. The dot stays in view over the
27
- sheet, which the User moves, resizes up to the window's edges and lets fill the window, and the
28
- page under it never changes, so leave nothing of the page to make room for it.
26
+ earlier visit, and later presses reopen the chat it last showed. The dot stays in view over the
27
+ sheet, which the User moves, resizes up to the window's edges and lets fill the window, and
28
+ whose list, closed at first, widens it to the left by the list's own width; the page under it
29
+ never changes, so leave nothing of the page to make room for it.
29
30
  `button.open(target)` opens the sheet, on `{ conversation }` or on a new chat with
30
31
  `{ agent }`, or as it was with no target, and with `docked: true` docked at full height along
31
32
  the window's right edge; `button.hide()` hides it, keeping its chat, scroll and typed text;
32
33
  `button.update(options)` and `button.unmount()` work as for a page, with `onOpen` and
33
- `onLink`. To open on text written in, write it as the chat's draft through the runtime's
34
+ `onLink`, though a Room document's link opens House in a new tab with that chat. To open on text written in, write it as the chat's draft through the runtime's
34
35
  `chatDraft` first, keyed `new:<agent>` for a new chat. In the sheet the input's + holds This
35
36
  page, which attaches the page's Page metadata to one message: a new chat's first message starts
36
37
  with it ticked. Mount one button per page.
@@ -42,9 +43,10 @@ Theme its own styles with CSS custom properties on the element or an ancestor:
42
43
  `--chat-accent`, `--chat-line`, `--chat-attention`, `--chat-danger`, `--chat-font`,
43
44
  `--chat-font-mono`, `--chat-font-size`, `--chat-radius` and `--chat-density` (a factor for
44
45
  spacing); an unset one keeps House's light or dark look. For the Chat button, which takes no
45
- element, set them on the document's root element. Its dot is House's and takes none of them.
46
+ element, set them on the document's root element. Its dot keeps House's colours and type, and
47
+ stays level with the sheet's input.
46
48
  It shows conversations only where the release declares them;
47
49
  otherwise it lists no conversation and no Agent. A secret
48
50
  question is never answered here; typed answers live in the Presentation State members named
49
- `typed:…`, whether the list shows in `chat:page-list`, and the sheet's size, place, list and
50
- open chat in `chat:sheet`: leave them.
51
+ `typed:…`, and the sheet's size, place, whether it fills the window and its open chat in
52
+ `chat:sheet`: leave them.
@@ -16,4 +16,6 @@ from a 360 pixel wide viewport to a desktop.
16
16
  Its strings are English only, dates and numbers follow the browser locale, and content is
17
17
  never translated. Light and dark themes come from CSS custom properties, defaulting to the
18
18
  browser's preference and respecting an App's override. What an agent adds carries none of
19
- these promises by itself. Data lives only in the App Creator's browser and the Provider.
19
+ these promises by itself. The App keeps no data of its own anywhere: its data lives in
20
+ its Rooms, at the Provider and on every device that holds them, and the
21
+ App's code sends it out only to the external origins its manifest declares.
@@ -6,7 +6,7 @@ How an App keeps what it shows as typed documents in the Rooms bound to it.
6
6
  - Keep each type in its own folder, such as `tasks/`, and read it by `pathPrefix`. A query naming `types` fails with `unknown-type` over bound Rooms none of which declares them, and answers `{ pending }` instead while the App reaches no bound Room yet; an observed query that has already answered answers `{ pending }` while its bound Rooms refuse it, and answers again once they declare what it names.
7
7
  - Link types with `reference` fields instead of copying values; a query follows them with `hop` and `backlinks`.
8
8
  - `app.json` `room_slots` names the Rooms the App needs, each with `access` `read` or `write` and the `types` it uses ([App manifest](reference/app-manifest)). Bind Rooms with `apps/bindings/set`; App code cannot bind and reaches only bound Rooms, so it opens `bridge.ceremony('/bind/<app_ref>?slot=<name>')`, the House page where your User binds Rooms, which returns to the App.
9
- - A bound Room must hold each slot type identically in its own `settings/schema.yaml`, beside any other types, or its binding warns the User who bound it, and `apps/bindings/list` names that release and slot in its `mismatch` line, to the App's owner too. Write it with `files_batch` before binding; `rooms/create` makes a new Room.
9
+ - A bound Room must hold each slot type identically in its own `settings/schema.yaml`, beside any other types, or its binding warns the User who bound it, and `apps/bindings/list` names that release and slot in a line of its `mismatches`, to the App's owner too. Only `indexed`, rule keywords and `title`s may differ, and a type whose `indexed` leaves out a field the App's names still fits, with a warning there naming the fields to index. Write it with `files_batch` before binding; `rooms/create` makes a new Room.
10
10
  - A slot may hold a Space instead (`apps/bindings/set` with `space_ref`): every Room of it your User holds, now and later. An App its owner puts into a Space (`apps/space/set`) uses that Space's schema instead of its own `schema.yaml`, and every member of the Space uses it, each slot holding the Space's Rooms.
11
11
  - Read an existing Room's `settings/schema.yaml` before inventing types. Two Apps over one schema are two interfaces over the same data, and your User's agents read and write those documents too.
12
12
  - House's own facts, such as your User's Rooms and the Space each is in, come from House acts: name each in `app.json` `house_acts` and call it with `runtime.call('<act>', input)`, such as `runtime.call('rooms/list', {})`. A call needs a connection, so offline show what the bound Rooms hold. A slot holding the Private Room brings its Triggers and none of the other documents House writes under its `house/`.
@@ -0,0 +1,10 @@
1
+ # Which function does what
2
+
3
+ Which part of House does each kind of an App's work, and what each run costs.
4
+
5
+ - **The page** reads and writes the bound Rooms on the device, offline too, and runs only while someone has it open ([Local first](apps/local-first)).
6
+ - **An App function** runs on House's servers when a Trigger of its Room fires, with no page open and no model. It reads and writes that Room and calls the services `external_origins` lists, within <function-time> and <function-memory> a run. It does what a script can: computing, filing, copying, counting, scheduling, calling a service ([App functions](apps/functions)).
7
+ - **An Environment function** is a script your User kept for one of their computers, which Kit runs there as them. It does work that needs that computer, its files or its logins. A Trigger of their Private Room, their Run now or `environments/functions/run` starts one; a Trigger of any other Room never does ([wizard](skills/wizard)).
8
+ - **An Agent function** starts a conversation of one of your User's Agents, which reads, judges and writes in minutes rather than milliseconds. Every run spends your User's own provider budget, and House sets no allowance on these runs: one Trigger acts on at most <trigger-events-per-hour> Events in a UTC hour, so a Room's Triggers together can still start many. It does what needs judgment: reading a message, answering it, deciding what a document means ([Agent function](reference/agent-function)).
9
+ - **A job that needs judgment in one step** splits: an App function does the rest, and an Agent function only that step, writing its answer into the Room, where a Trigger hands it on to the App function ([Agent function](reference/agent-function)).
10
+ - **With no page open**, nothing of the App runs by itself: a Trigger whose action notifies someone or runs one of these starts its work ([Triggers](reference/triggers)).
@@ -0,0 +1,14 @@
1
+ # Designs that load House
2
+
3
+ What an App's saves, Triggers and functions cost House's servers and every device holding its Rooms.
4
+
5
+ - **A save** stores the document's whole new text as a revision, tests it against every Trigger of its Room inside the write, streams the whole text to every device holding the Room and counts once against the saver's Write limit, whatever its size. A log or a state kept in one growing document costs its whole size on every change: keep one small document per thing.
6
+ - **Saving as someone types**, or on a timer, writes each time; the Room Draft keeps what they type until they save ([Room Drafts](apps/room-drafts)).
7
+ - **Every device** holding a Room at the Keep or Open level holds all of its text ([Access level](reference/access-level)), so records written by the thousand fill every holder's device; a file goes in as a file ([files](apps/runtime-files)), never as text in a document.
8
+ - **A Trigger** whose change test is any edit, `changes: "text"`, acts on every save of a document it matches, while a field's change, or the default firing as a document starts to match, acts once per change that matters ([Triggers](reference/triggers)).
9
+ - **A function's writes** fire Triggers, its own included: one whose write matches its own Trigger runs again on each run until that Trigger's <trigger-events-per-hour> Events in the hour are spent, so write what it writes so that its Trigger does not match it ([App functions](apps/functions)).
10
+ - **A function run** reads, on every Event, every document of the types, folders and paths its query names from House's database, within <function-memory>, and one Room owner's runs take at most <function-owner-slots> slots on a server at once: name the narrowest folder or paths, or read a summary document the function keeps ([App functions](apps/functions)).
11
+ - **One Trigger's Events** run one after another, so a busy Trigger builds a queue ([App functions](apps/functions)).
12
+ - **An Agent function run** spends your User's provider budget each time ([Agent function](reference/agent-function)).
13
+ - **Changing a field's `title`** relabels every document of its type in one write ([Schema](reference/schema)).
14
+ - **A query naming no `types`, `pathPrefix` or `paths`** reads every document of its Rooms on the device: name the narrowest and select only what a screen shows ([Local Query](apps/local-query)).
@@ -0,0 +1,11 @@
1
+ # An App's look
2
+
3
+ What House draws around an App's page, and what the App sets for its look.
4
+
5
+ - **The page is the App's own**, drawn and run by the App ([An App's page](apps/page)). Over it House draws only its wait until the page shows its place, and the dialog asking a User who does not own the App to allow a browser feature or House act the App uses for the first time.
6
+ - **The Chat**, where the page mounts it, draws in the App's frame: the Chat button's dot in House's look, and the Chat in its own, which the `--chat-*` properties theme ([The Chat](apps/chat)).
7
+ - **The design system's tokens**: mdui's `style.css` holds House's design tokens as CSS custom properties, light and dark: its greys, type sizes, spacing, radii and motion. An App built on them looks like House; changing them gives it a look of its own. The design system itself is not published, so mdui's tokens are how an App takes it.
8
+ - **Light and dark**: House's screens around the page, such as its wait until the page shows its place, follow the device's light or dark setting, as `prefers-color-scheme` does.
9
+ - **The top colour**: declare the page's top colour as `theme_colour` in `app.json`, and `theme_colour_dark` where the dark one differs ([App manifest](reference/app-manifest)). An installed App's title bar and a phone's top strip take it, and House's white or near-black ground without it. A page with its own light and dark switch calls `bridge.setTab({ scheme })` with `'light'` or `'dark'` when it shows one over the device's setting, and `null` when it follows the device, so they take that scheme's colour.
10
+ - **The icon** is the owner's setting, never the release's: one image in the browser tab, the panel and on a home screen. It is the App cube in the App's colour until one is set, then a generated one from one to four colours or the owner's own square PNG or SVG, through `apps/tab-icon/set`; `bridge.setTab({ title, icon })` changes the open tab's title and icon until the next load.
11
+ - **No focus mark**: House's own screens draw none, and [An App's page](apps/page) says how a page removes the browser's ring.
@@ -0,0 +1,13 @@
1
+ # Schemas that read well
2
+
3
+ How an App's types make documents that read well as Markdown, to people and agents alike.
4
+
5
+ A typed document writes its values as Markdown, so its schema decides how it reads; [Schema](reference/schema) holds the format.
6
+
7
+ - A field named `name` is the document's `#` line, and a type with none writes documents with no title line.
8
+ - `title` on a field is the label its line shows, and a field with none shows its key.
9
+ - Each field takes one JSON type. A short value is one line, a yes or no a checkbox line and a `reference` a link; Markdown text (`contentMediaType: text/markdown`) and a value that may hold a line break take a heading of their own; a value of no single type is written as a fenced `json` block.
10
+ - Rows of short values read as a table, and a list of groups that is no table takes one heading per item, named by its `name`.
11
+ - Nest values at most five levels deep, each list's items counting as a level. A value that takes a heading goes one heading level deeper per level, from `##` to `######`, so a schema that places one deeper is refused `heading-depth`, and a deeper plain string that holds a line break is refused `malformed-value` when it is written.
12
+ - One document is one thing, such as a task, a lead or an order: link others by `reference` rather than copying or nesting them, and keep a growing list of records as a folder of documents.
13
+ - A type whose Room your User holds at the Search level, which the App asks through a [House query](apps/query-house), names in `indexed` the fields that query orders, totals or groups by, a list of its own top-level keys, and House keeps an index of their values. A field there holds one plain value, `null` allowed: a number, an integer, a yes or no, or a string that is a choice, a `date`, a `date-time`, a `time` or bounded by a `maxLength` of at most <indexed-text-length> characters; a list, a group, a value of no single type, Markdown text, a `reference` and a House key such as `member` or `submitted` are not. A type names at most <indexed-fields>, and House refuses a schema naming more, an unknown field or one it cannot index with the rule `indexed` and its place. Naming one changes no document and checks none again, so whoever writes the schema may change it.
@@ -0,0 +1,12 @@
1
+ # Design an App
2
+
3
+ What an App's design settles before its code, one page per question House answers.
4
+
5
+ - [Which function does what](apps/design-functions): which part of House does each piece of the App's work, and what it costs.
6
+ - [Schemas that read well](apps/design-schema): how its types make documents that people and agents read.
7
+ - [Designs that load House](apps/design-load): what its saves, Triggers and functions cost, and the designs that cost less.
8
+ - [An App's look](apps/design-look): what House draws around its page, and what the App sets.
9
+
10
+ Keep a Room for the App where you write how it is developed: its decisions, its plan and tickets, and what its Triggers and functions do. Name that Room in the App's README, so the next agent working on the App finds it. House reads nothing there.
11
+
12
+ [Example: tasks](apps/example-tasks) follows these pages.
@@ -12,8 +12,8 @@ function taskCreation(draft: Draft) {
12
12
  : undefined;
13
13
  }
14
14
 
15
- function titleOf(draft: Draft): unknown {
16
- return draft?.parts.fields?.title?.value ?? taskCreation(draft)?.fields.title;
15
+ function nameOf(draft: Draft): unknown {
16
+ return draft?.parts.fields?.name?.value ?? taskCreation(draft)?.fields.name;
17
17
  }
18
18
 
19
19
  function el<K extends keyof HTMLElementTagNameMap>(
@@ -26,29 +26,29 @@ function el<K extends keyof HTMLElementTagNameMap>(
26
26
  return node;
27
27
  }
28
28
 
29
- function titleForm(
29
+ function nameForm(
30
30
  action: string,
31
31
  room: string,
32
32
  path: () => string,
33
33
  shown: unknown,
34
- parts: (title: string, held: Draft) => Parts,
35
- done: (title: HTMLInputElement) => void,
34
+ parts: (name: string, held: Draft) => Parts,
35
+ done: (name: HTMLInputElement) => void,
36
36
  ) {
37
37
  let held = drafts.current().find((draft) => draft.room === room && draft.path === path()) ?? null;
38
- const title = el('input', {
39
- value: String(titleOf(held) ?? shown),
38
+ const name = el('input', {
39
+ value: String(nameOf(held) ?? shown),
40
40
  required: true,
41
- ariaLabel: 'Title',
41
+ ariaLabel: 'Name',
42
42
  });
43
43
  const keep = () =>
44
44
  queued(async () => {
45
- const written = await write(room, path(), held, parts(title.value, held));
45
+ const written = await write(room, path(), held, parts(name.value, held));
46
46
  held = written.draft;
47
- if (written.stale) title.value = String(titleOf(held) ?? shown);
47
+ if (written.stale) name.value = String(nameOf(held) ?? shown);
48
48
  return written.stale;
49
49
  });
50
- title.oninput = () => void keep();
51
- const form = el('form', {}, title, el('button', {}, action));
50
+ name.oninput = () => void keep();
51
+ const form = el('form', {}, name, el('button', {}, action));
52
52
  form.onsubmit = async (event) => {
53
53
  event.preventDefault();
54
54
  const stale = await keep();
@@ -56,16 +56,16 @@ function titleForm(
56
56
  if (stale) return void (note.textContent = 'Not saved: stale.');
57
57
  if (!(await save(room, held))) return;
58
58
  held = null;
59
- done(title);
59
+ done(name);
60
60
  };
61
61
  return form;
62
62
  }
63
63
 
64
64
  function editForm(room: string, path: string, shown: unknown) {
65
- const parts = (title: string): Parts => ({
66
- fields: { title: { value: title, base: { value: shown } } },
65
+ const parts = (name: string): Parts => ({
66
+ fields: { name: { value: name, base: { value: shown } } },
67
67
  });
68
- return titleForm(
68
+ return nameForm(
69
69
  'Save',
70
70
  room,
71
71
  () => path,
@@ -76,22 +76,22 @@ function editForm(room: string, path: string, shown: unknown) {
76
76
  }
77
77
 
78
78
  function addForm(room: string) {
79
- const parts = (title: string, held: Draft): Parts => {
79
+ const parts = (name: string, held: Draft): Parts => {
80
80
  const kept = taskCreation(held) ?? { type: 'task', fields: { done: false }, sections: {} };
81
- return { creation: { ...kept, fields: { ...kept.fields, title } } };
81
+ return { creation: { ...kept, fields: { ...kept.fields, name } } };
82
82
  };
83
83
  const adding = drafts
84
84
  .current()
85
85
  .find((draft) => draft.room === room && draft.path.startsWith('tasks/') && taskCreation(draft));
86
86
  let path = adding?.path ?? `tasks/${crypto.randomUUID()}.md`;
87
- return titleForm(
87
+ return nameForm(
88
88
  'Add',
89
89
  room,
90
90
  () => path,
91
91
  '',
92
92
  parts,
93
- (title) => {
94
- title.value = '';
93
+ (name) => {
94
+ name.value = '';
95
95
  path = `tasks/${crypto.randomUUID()}.md`;
96
96
  },
97
97
  );
@@ -23,7 +23,7 @@ function draw(): void {
23
23
  note.textContent = '';
24
24
  if (room === undefined) main.replaceChildren('This App has no Room yet.');
25
25
  else if (open !== undefined)
26
- main.replaceChildren(editForm(open.room, open.path, open.fields!.title), note);
26
+ main.replaceChildren(editForm(open.room, open.path, open.fields!.name), note);
27
27
  else main.replaceChildren(list, addForm(room.locator), note);
28
28
  }
29
29
  if (!('pending' in answer)) shown();
@@ -35,7 +35,7 @@ function draw(): void {
35
35
  const done = task.fields!.done === true;
36
36
  const box = el('input', { type: 'checkbox', checked: done, ariaLabel: 'Done' });
37
37
  box.onchange = () => void queued(() => toggle(task.room, task.path, done));
38
- const link = el('a', { href: task.path }, String(task.fields!.title));
38
+ const link = el('a', { href: task.path }, String(task.fields!.name));
39
39
  link.onclick = (event) => {
40
40
  event.preventDefault();
41
41
  void bridge.push(`/${task.path}`);
@@ -2,14 +2,14 @@
2
2
 
3
3
  A whole tasks App over one Room, keeping every rule of the App pages; its `main.ts` goes on over two more pages.
4
4
 
5
- `schema.yaml`, also written into the Room's `settings/schema.yaml`:
5
+ `schema.yaml`, also written into the Room's `settings/schema.yaml`, so a task reads as its `# Buy milk` line over a `- [ ] Done` line:
6
6
 
7
7
  ```yaml
8
8
  task:
9
9
  properties:
10
- title: { type: string }
11
- done: { type: boolean }
12
- required: [title, done]
10
+ name: { type: string }
11
+ done: { type: boolean, title: Done }
12
+ required: [name, done]
13
13
  ```
14
14
 
15
15
  `app.json`, then `apps/bindings/set` with slot `tasks`:
@@ -34,8 +34,8 @@ const sync = await runtime.observe('sync-status');
34
34
  const drafts = await runtime.observe('room-drafts');
35
35
  const tasks = await runtime.observe('query', {
36
36
  pathPrefix: 'tasks',
37
- select: { fields: ['/title', '/done'] },
38
- orderBy: [{ by: { field: '/title' } }],
37
+ select: { fields: ['/name', '/done'] },
38
+ orderBy: [{ by: { field: '/name' } }],
39
39
  });
40
40
  const main = document.querySelector('main')!;
41
41
  const list = document.createElement('div');
@@ -2,12 +2,12 @@
2
2
 
3
3
  How an App declares a function that House runs on its servers when a Trigger fires, with no page open and no model.
4
4
 
5
- - **Declare.** `app.json` lists `functions`, each `{"name": "assign lead", "entry": "functions/assign.js"}` ([App manifest](reference/app-manifest)). The entry is one ES module whose default export is the function and which imports nothing, so your build bundles each function, with all it imports, into `dist/`, such as `esbuild src/assign.ts --bundle --format=esm --outfile=dist/functions/assign.js`. A publish is refused whole for an entry that is missing, imports, has no default export or is over <function-size>, for a name used twice, and for more than <release-functions> functions.
6
- - **Run.** A Trigger's `function` action names the App and one of its functions ([Triggers](reference/triggers)). House runs the App's latest release when the Event's turn comes, one Event of a Trigger after another, so a run reads what the run before it wrote. Runs share a fixed number of slots on each server, one Room owner's at most <function-owner-slots>, and a run waits, its action `waiting`, until it may take one.
7
- - **The event.** `export default async (event, house) => {}` receives `kind`, the condition's `on`, with `room`, `trigger`, `id`, `moment`, the Room's `time_zone`, `date` and `time` ([Times](apps/function-times)) and what matched: `path` and `reference`, a document change's `transition`, an arriving item's `item` with its `headers` and raw `body` text, a dated document's `document`, a delivered notification's `notification`, the facts of its web document, or a message's `message`, its `sender` and its request's `agent_function`, never its words, with the document that request was started for as `path` and `reference`. `Date` reads `moment`. The return value is ignored, so write what you keep into the Room.
8
- - **The App API.** `house` is the page's App API over the Trigger's Room alone: `query(q)` ([Local Query](apps/local-query)), which reads only the documents of the `types`, folders (`folder`, `pathPrefix`) and `paths` it names and answers over them alone, so the rest of a large Room is never read: name the `types` or `pathPrefix` of its `hop` and `backlinks` too, and select its parts, since a query or `hop` or `backlinks` naming none, or selecting `backlinks` or `'all'`, is refused `invalid-query`, `saveParts(room, parts)`, `call(name, input)` for the Room acts, each by its House act name, such as `rooms/members/list` and `triggers/fired/list`, and `callExternal(request)`. `call` answers a refusal as a result whose `kind` is `rejected` and `saveParts` as `outcome: 'refused'`, as on the page, and the others reject it with its `code`. Its writes are the App's, by `App <App> (function <function>)`, fire Triggers, its own included, and count against the Room owner's Write limit.
9
- - **Reach.** A run holds no `fetch`, timer, file, `console` or `Intl`: convert times by `house.toMoment` and `house.toLocal`, which a page lacks. `callExternal` takes the request `bridge.callExternal` takes and is the one way out, to an origin the release's `external_origins` lists; naming its Room, `room_ref: event.room`, carries that Room's Service key, which the function never sees, or is refused `not_found` naming the `origin`. Its calls count against the Room owner's `external_calls` limit.
10
- - **Never repeated.** A run that started is never run again, even when it failed after a call reached the service. Send `event.id` as the service's idempotency key where it takes one, and name what you write by what the event carries, so a second delivery of the same thing finds it.
11
- - **Failure and limits.** A throw settles the run `failed` with `function_failed` and its text, which `triggers/fired/list` shows and the panel shows the Room's owner, with no notification; the next Event still runs. A run ends `failed` at <function-time>, at <function-memory> or past <function-calls> external calls: `time_cap_reached`, `memory_cap_reached` or `external_call_cap_reached`.
5
+ - **Declare.** `app.json` lists `functions`, each `{"name": "assign lead", "entry": "functions/assign.js"}` ([App manifest](reference/app-manifest)). The entry is one ES module that imports nothing and default-exports the function, so your build bundles each function with its imports into `dist/`, such as `esbuild src/assign.ts --bundle --format=esm --outfile=dist/functions/assign.js`. A publish is refused whole for an entry that is missing, imports, has no default export or is over <function-size>, for a name used twice, and for more than <release-functions> functions.
6
+ - **Run.** A Trigger's `function` action names the App and one of its functions ([Triggers](reference/triggers)). House runs the App's latest release when the Event's turn comes, one Event of a Trigger at a time, so a run reads what the one before wrote. Runs share a fixed number of slots on each server, one Room owner's at most <function-owner-slots>, and a run waits, its action `waiting`, until it may take one.
7
+ - **The event.** `export default async (event, house) => {}` receives `kind`, the condition's `on`, with `room`, `trigger`, `id`, `moment`, the Room's `time_zone`, `date` and `time` ([Times](apps/function-times)) and what matched: `path` and `reference`, a document change's `transition`, an arriving item's `item` with its `headers` and raw `body` text, a dated document's `document`, a delivered notification's `notification`, the facts of its web document, or a message's `message`, its `sender` and its request's `agent_function`, never its words, with the document that request was started for as `path` and `reference`. `Date` reads `moment`. The return value is ignored: write what you keep into the Room.
8
+ - **The App API.** `house` is the page's App API over the Trigger's Room alone: `query(q)` ([Local Query](apps/local-query)), which reads only the documents of the `types`, folders (`folder`, `pathPrefix`) and `paths` it names: name the `types` or `pathPrefix` of its `hop` and `backlinks` too and select its parts, since naming none, or selecting `backlinks` or `'all'`, is refused `invalid-query`, `saveParts(room, parts)`, `call(name, input)` for the Room acts by House act name and `callExternal(request)`. `call` answers a refusal as `kind: 'rejected'` and `saveParts` as `outcome: 'refused'`, as on the page; the others reject with its `code`. Its writes are the App's, by `App <App> (function <function>)`, fire Triggers, its own included, and count against the Room owner's Write limit.
9
+ - **Reach.** A run holds no `fetch`, timer, file, `console` or `Intl`: convert times by `house.toMoment` and `house.toLocal`, which a page lacks. `callExternal` takes the request `bridge.callExternal` takes and is the one way out; naming its Room, `room_ref: event.room`, carries that Room's Service key, which the function never sees; calls count against the Room owner's `external_calls` limit. Its webhook signing secret goes nowhere: `call('service-keys/signatures/check', { room_ref, origin, text, signature })` answers `matched`, whether `signature` is `text`'s HMAC-SHA256 under it, lowercase hex or base64. Both take only an origin the release's `external_origins` lists and are refused `not_found` naming the `origin` where the Room holds none.
10
+ - **Never repeated.** A run that started is never run again, even when it failed after a call reached the service. Send `event.id` as the service's idempotency key where it takes one, and name what you write by what the event carries, so a second delivery finds it.
11
+ - **Failure and limits.** A throw settles the run `failed` with `function_failed` and its text, which `triggers/fired/list` and the Room owner's panel show, with no notification; the next Event still runs. A run ends `failed` at <function-time>, at <function-memory> or past <function-calls> external calls: `time_cap_reached`, `memory_cap_reached` or `external_call_cap_reached`.
12
12
 
13
13
  Next: [Example: assign a lead](apps/example-function)
@@ -2,16 +2,16 @@
2
2
 
3
3
  How an App reads and writes its Rooms on the device, offline too.
4
4
 
5
- - The runtime holds every bound Room on the device and streams in every change from any device, App or agent. `query` and `observe` answer from the device and never wait on the network; `search` asks House ([Search rules](apps/search)).
6
- - Draw from `observe` at once, with no request of your own; an observation hears each later change by itself, so never poll, refetch or subscribe. An answer is `{ pending }` while a Room it reads has not reached this device or been prepared on it: draw the page without those documents; keep the empty state for a complete answer. `rooms` answers once the runtime knows the Rooms the page reaches. Offer binding a Room when it is empty while the sync status's `providerUnreachable` is false and `observe('connection-refusal')` answers `null`; any other empty list says only that nothing can be listed yet.
5
+ - The runtime holds every bound Room on the device and streams in every change from any device, App or agent, but a Search-level Room, which an App shows only as the rows [`queryHouse`](apps/query-house) asks for, never offline. `query` and `observe` answer from the device and never wait on the network; `search` asks House ([Search rules](apps/search)).
6
+ - Draw from `observe` at once; an observation hears each later change by itself, so never poll, refetch or subscribe to House. An answer is `{ pending }` while a Room it reads has not reached this device or been prepared on it: draw the page without those documents; keep the empty state for a complete answer. `rooms` answers once the runtime knows the Rooms the page reaches. Offer binding a Room when it is empty while the sync status's `providerUnreachable` is false and `observe('connection-refusal')` answers `null`; any other empty list says only that nothing can be listed yet.
7
7
  - Show no loading state: draw the last state the device holds. A wait shows only after an act the User pressed whose answer comes from outside the stream, such as `bridge.callExternal`, until it arrives.
8
8
  - Write optimistically: write a Room Draft, then `save`. The change shows in every query at once as a Pending Change and reaches House when it can. `save` refuses at once, with its `code`, a change that is `stale`, `changed-underneath` or against the schema: say so and keep the form. After `changed-underneath`, draw the joined Room Draft and its `marks` ([Room Drafts](apps/room-drafts)).
9
9
  - Show what the runtime reports: `observe('queue', room)` lists changes House has not confirmed yet, and `observe('sync-status')`'s `attentionRequired` lists each change House refused, returned as a Room Draft with why, to save again or discard.
10
- - Keep what the User types in the Room Draft (`roomDraft.write`) as they type; it survives a new release replacing the page, a reload and another tab. Draw a form again only when its page changes, never on a streamed change.
10
+ - Keep what the User types in the Room Draft (`roomDraft.write`) as they type; it survives a new release, a reload and another tab. Draw a form again only when its page changes, never on a streamed change.
11
11
  - A document has one Room Draft, shared by every tab and App, and `save` takes all of it. A write replaces every part and names the revision it read, so write onto the parts it holds; one from an older revision answers `stale` with the current Room Draft: show that one. A field part names as its `base` the value the page showed, `{ value }`, or `null` when the document has no such field.
12
12
  - Save a change the User does not type, such as a checkbox, with `saveParts(room, [{ path, parts }])`: it writes no Room Draft, so the one another tab holds stays.
13
- - Offline, all of this works and saves wait; `bridge.onStatus` reports `offline`: say so quietly.
14
- - A Room your User holds at the Open level locks once the browser has been offline 2 minutes, and from the moment it opens until House confirms the access. While it is locked, `rooms` lists it `locked: true`, a query that does not name it leaves it out, one naming it answers `{ pending }` with `awaits: 'unlock'`, and every other read and write naming it, `save` included, answers the code `room-locked` and keeps nothing. Draw it locked until it unlocks and keep what the User types in the page; what was saved before stays and is sent then.
15
- - An outside service is reached only at the origins `app.json` lists in `external_origins`, directly or through `bridge.callExternal`, sent by House's server. A call that names a Room bound to the App in a `write` slot with `room_ref` carries that Room's Service key for the origin, which the page never sees; any other call carries no key. A call through a Room that holds no key for the origin is refused `not_found` naming the `origin`: tell the person that the Room's owner or a manager enters the key under Connected on the Room's Settings page, since no App opens that page.
13
+ - Offline, all of this works for held Rooms and saves wait; `bridge.onStatus` reports `offline`: say so quietly.
14
+ - A Room your User holds at the Open level locks once the browser has been offline 2 minutes, and from the moment it opens until House confirms the access. While it is locked, `rooms` lists it `locked: true`, a query that does not name it leaves it out, one naming it answers `{ pending }` with `awaits: 'unlock'`, and every other read and write naming it, `save` included, answers the code `room-locked` and keeps nothing. Draw it locked until then and keep what the User types; what was saved before is sent then.
15
+ - An outside service is reached only at the origins `app.json` lists in `external_origins`, directly or through `bridge.callExternal`, sent by House's server. A call whose `room_ref` names a Room bound in a `write` slot carries that Room's Service key for the origin, unseen by the page; any other carries none. Through a Room holding no key for the origin it is refused `not_found` naming the `origin`: tell the person its owner or a manager enters the key under Connected in the Room's Settings, which no App opens.
16
16
  - The page's own storage is a disposable cache, the Room the only durable home of the App's data; handle any code or value you do not know by its `code` ([Refusals](apps/runtime-refusals)).
17
17
  - React: `useObserve` and `useSave` from `@agentshouse/mdruntime/react`. The whole client: [The App runtime](apps/runtime).
@@ -7,9 +7,9 @@ hears it again only when it changes, keeping every unchanged Document the same o
7
7
  a component can tell what changed by identity. `{ documents, coverage }` or `{ groups,
8
8
  coverage }` is complete for the Rooms its `coverage` names, and `{ pending }` names each
9
9
  Room not yet acquired, prepared or unlocked, `[{ room, awaits }]`: never read it as empty,
10
- draw it as loading and observe the query to hear the complete answer. A query is data:
10
+ draw them absent and observe the query to hear the complete answer. A query is data:
11
11
 
12
- - **Filter.** `rooms` names Room locators and defaults to every Room. `types` names MD
12
+ - **Filter.** `rooms` names Room locators and defaults to every bound Room but a Search-level or locked one, which `pending` omits. `types` names MD
13
13
  Types, and `any` names untyped Markdown Documents. `pathPrefix` is a directory: `notes`
14
14
  keeps `notes/a.md` and not `notes2/a.md`; `outside` names directories whose contents stay
15
15
  out, `['house']` leaving `house/` out; `folder` keeps only what lies directly in it.
@@ -2,13 +2,12 @@
2
2
 
3
3
  What an App's page draws and how it behaves in the browser.
4
4
 
5
- - The App draws everything; House draws nothing around it. Set the tab's title with `bridge.setTab({ title })`.
6
- - The address bar is the shell's, not your page's: read your place from `bridge.address()` and `bridge.onAddress`, and move with `bridge.push` or `bridge.replace`, so Back, Forward, reload and a shared link keep it; a push to the address already shown replaces it, so a double click adds no history entry. After an act that ends what the page shows, and when a dialog closes, `bridge.replace` the next address; a replace with the address this page was opened from goes back to it, so Back returns to where the person came from. Keep the open item, tab, filter and search in the address. Addresses under `/render` and `/info` are House's.
5
+ - The App draws everything on its page; [An App's look](apps/design-look) says what House draws around it. Set the tab's title with `bridge.setTab({ title })`.
6
+ - The address bar is the shell's, not your page's: read your place from `bridge.address()` and `bridge.onAddress`, and move with `bridge.push` or `bridge.replace`, so Back, Forward, reload and a shared link keep it; a push to the address already shown replaces it, so a double click adds no history entry. After an act that ends what the page shows, and when a dialog closes, `bridge.replace` the next address; a replace with the address this page was opened from goes back to it, so Back returns to where the person came from. Keep the open item, tab, filter and search in the address. Addresses under `/render`, `/info` and `/home-screen` are House's.
7
7
  - Files: take one in with `binaryFile.attach` the moment it is chosen and keep the Path it answers, hand one out with `binaryFile.download`, and show an image or video in `<room-media>` naming its width and height, so nothing waits for media and nothing moves when it arrives; never read a file whole to show or download it ([files](apps/runtime-files)).
8
8
  - Mobile first: lay out from 360 pixels wide up, with no action behind hover alone.
9
9
  - Every control works from the keyboard and has a label. Draw no focus mark: give nothing a focus style, and remove the browser's own ring with `* { outline: none; }`, the reset House's base carries, since that base does not reach your page. Nothing takes focus by itself: no `autofocus`, and no `focus()` that runs because a page or dialog opened.
10
10
  - An empty list says what it will hold and invites the first document.
11
11
  - Long names truncate with an ellipsis and show in full on hover.
12
12
  - The element is the link: a row, card or name opens its item; no "Open" buttons.
13
- - Offer installation yourself: `bridge.install()` from a click shows the browser's prompt, or answers `manual` where you show how.
14
13
  - Offer a way out where your User expects one: `bridge.ceremony('/sign-out')` signs them out of House and opens sign-in.
@@ -0,0 +1,48 @@
1
+ # House query rules
2
+
3
+ How `queryHouse` asks House for pages of rows of a Room the device never holds.
4
+
5
+ A Room your User holds at the Search level never reaches the device. `rooms` lists it with
6
+ `queriesOnly: true` and nothing held, a [Local Query](apps/local-query) naming it fails with
7
+ the code `queries-only`, and one naming no Room leaves it out; its writes, history, files and
8
+ `resolve` answer as for a Room the App does not bind.
9
+
10
+ - **Asking.** `queryHouse(query)` takes a Document or group query in the language of a Local
11
+ Query whose `rooms` names that one Room, and asks House once, online. Nothing observes it: no
12
+ `observe`, hook or `readAgain` asks it again, so ask again when the page needs the rows
13
+ again, such as when the person scrolls, opens it again or presses refresh. `call` reaches no
14
+ `rooms/query`.
15
+ - **A page.** `{ kind: 'rows', documents, next }`: at most 100 rows, read in Path order unless
16
+ it orders by an indexed field, ascending or with
17
+ `orderBy: [{ by: 'path', direction: 'desc', stringSort: 'lexical' }]`.
18
+ Pass `next` as `after` for the next page. A page may hold fewer rows than `limit`, or none,
19
+ and still name `next`: only an answer without `next` is the end.
20
+ - **Ordering.** A query may order by a field each of its `types` names in its schema's
21
+ `indexed`, as `orderBy: [{ by: { field: '/price' }, direction: 'desc', nulls: 'last' }]`, a
22
+ text field with `stringSort: 'lexical'`, by code point. Ties order by Path, and `next` is the
23
+ continuation a Local Query names, passed as `after` the same way. In Path order, an equality or
24
+ `$in` on such a field reads only its documents, and every other test still filters each page.
25
+ A Room on the device still orders, totals and groups by any field through a Local Query.
26
+ - **Totals.** `total: true` adds `total` where every field the query tests is indexed so and it
27
+ tests no `text`. House counts at most its count cap of documents: past it, `total` counts those
28
+ it read and `totalCapped: true` marks it as a floor, so show it as at least that many.
29
+ - **Groups.** `groupBy: [{ field: '/brand' }]` on one such field answers
30
+ `{ kind: 'groups', groups, next }`, each group its `key` and `count`, at most 100 a page in key
31
+ order or with `orderBy: [{ by: 'count', direction: 'desc' }]`; pass `next` as `after`.
32
+ `capped: true` marks counts past the cap as floors.
33
+ - **Refusals.** House answers `{ kind: 'rejected', code, fields }` for a part that would read the
34
+ whole Room, as `invalid-query` with a `message` naming the part: an order by more than one term
35
+ or by anything but Path or such a field, an order, total or group without `types` or on a field
36
+ a named type does not index, a total or group testing `text` or a field not indexed, a total
37
+ beside `groupBy`, a group's `size`, `updated` or `greatest`, `hop`, `backlinks`, `fileIds`,
38
+ `select: 'all'` or a `limit` of 0. A query over 8,000 bytes is `query_too_large` with its
39
+ `cap`: fix the query.
40
+ - **Offline.** Without a connection `queryHouse` answers `{ kind: 'offline' }` at once: show
41
+ nothing of that Room and say it needs a connection. A lost answer is
42
+ `transport-uncertainty`, which the page may ask again, and where no House query is reached,
43
+ `{ kind: 'undeclared' }`.
44
+ - **Other Rooms.** A Room the device holds answers `rejected` with the code
45
+ `held-on-device`: read it with a Local Query. A Room the App does not reach answers
46
+ `room-not-readable`.
47
+ - **Nothing kept.** The rows, totals and groups reach the page alone: the device keeps none of
48
+ them, so they are gone once the page closes, and asking again shows what the Room holds then.
@@ -3,7 +3,7 @@
3
3
  What App code reaches through the runtime's client: its surfaces, its reach, observations, Agents and conversations.
4
4
 
5
5
  App code reaches the MD Runtime through the remote client. The surfaces are Rooms, Query,
6
- Search, Observe, Types, Room Draft, Save, History, Resolve, binary files, Call,
6
+ Search, House query, Observe, Types, Room Draft, Save, History, Resolve, binary files, Call,
7
7
  Reached, the reported persistence answer, reauthorization, conversation, the chat
8
8
  draft, and Presentation State. Sync Status, readiness, the Service Mode, the storage-loss
9
9
  statement, the deployment facts, the signed-in member and the count of authority changes
@@ -11,7 +11,8 @@ are observations only.
11
11
 
12
12
  The host attaches each port with its reach, and every member and observation answers
13
13
  within it: a Room outside it answers as one the replica does not hold, Rooms lists only
14
- the Rooms reached whole, a Room reached for single Documents shows only them and their
14
+ the Rooms reached whole and, after them with `queriesOnly: true`, those its User holds at the
15
+ Search level, which only `queryHouse` reads ([House query rules](apps/query-house)), a Room reached for single Documents shows only them and their
15
16
  models' Types, and without conversations the conversation members, chat draft and
16
17
  observation hold none. A changed reach reaches every open observation as its new value.
17
18
 
@@ -26,8 +27,9 @@ not, each with its `cli` and `environment` `{ identity, name, state }`, `state`
26
27
  `offline`; offline every Agent a conversation names. Each conversation the Provider lists is held
27
28
  complete in the browser. `conversation.list()` and `observe('conversations')` answer each
28
29
  with its Agent's identity and name, its name, created and finished times, last outcome,
29
- live `state` (`asking` while a question waits, `working` while a turn runs, else `idle`)
30
- and `started`, when the running turn began, or `null`. `observe('conversation', id, {
30
+ live `state` (`asking` while a question waits, `working` while a turn runs, else `idle`),
31
+ `started`, when the running turn began, or `null`, `jobs`, the kinds and counts of what a turn
32
+ left running, and `deliveryWaiting`, the messages no turn has taken yet. `observe('conversation', id, {
31
33
  limit, total: true })` holds its latest `limit` messages, at most 1,000, oldest first,
32
34
  `total` their count, and `next` while older ones exist, which `after` takes, there or in
33
35
  `conversation.read(id, { limit, after })`, for the window before; its chat draft; and,
@@ -6,16 +6,16 @@ Presentation State is one value per App, kept in the browser and shared by its t
6
6
  `presentation.read()` answers it, `null` before anything is kept,
7
7
  `observe('presentation')` hears it whole, `presentation.write(value)` replaces it whole,
8
8
  and `presentation.edit(members)` sets each member named, or removes one named `null`, in
9
- the state as it stands, so tabs changing different members never lose each other's change:
9
+ the state as it stands, so tabs editing different members keep each other's change:
10
10
  never write back a value read before. Each rejects when the browser does not keep the
11
11
  change, and no tab sees it then.
12
12
 
13
13
  A binary file at a Path is a binary Path entry. Query lists it by `path`, and History each
14
14
  of its revisions, with its `binaryFile` record and never its bytes: `{ state: 'accepted',
15
- revision, path, byteLength, mediaType, sha256 }`, or `{ state: 'pending', localId, path,
16
- byteLength, mediaType, sha256 }` while an upload the panel saved waits for the Provider.
17
- Text names a file only by a link to its Path, and a field by a `reference: attachment`. Move
18
- or remove one as a Document, its bytes following.
15
+ revision, path, byteLength, mediaType, sha256, width, height }`, or with `state: 'pending'`
16
+ and a `localId` for `revision` while an upload the panel saved waits for the Provider.
17
+ House's pages keep an image's proportions by them, an Entry Page `srcset` and `picture`.
18
+ Text names a file only by a link to its Path, a field by a `reference: attachment`. Move or remove one as a Document, its bytes following.
19
19
 
20
20
  - `binaryFile.attach(room, file)` writes the `File` into the Room at
21
21
  `attachments/<YYYY-MM-DD>/<name>`, a free name, and answers `{ kind: 'attached', path }`,
@@ -26,27 +26,27 @@ or remove one as a Document, its bytes following.
26
26
  own download of the Provider's address at once, or of a pending file's saved bytes, and
27
27
  answers `{ kind: 'started' }`, `blocked` where the browser refused the tab an `.html`
28
28
  page opens in, or a refusal or failure.
29
- - `<room-media room path width height alt>` draws an image, video or audio file of the
30
- Room. It reserves the box `width` and `height` set, in the file's own pixels, or a 16:9
31
- one, before any byte; asks for the file only as it nears the view; draws a file the
32
- browser keeps from its bytes, offline too, and reads and keeps an image; shows a video's
33
- first frame with a play mark and plays it only in House's full-screen viewer, which a
34
- press on it opens, and plays an audio file, loading only either's metadata from the
35
- Provider's address; asks again when a shown file's address lapses; asks again while the
36
- file cannot be shown yet, such as
37
- while the Room does not hold it or House did not answer, until House refuses it for
38
- good; and shows its children where the file cannot be read.
29
+ - `<room-media room path width height alt>` draws an image, video or audio file. Before
30
+ any byte it reserves the box `width` and `height` set in the file's pixels, else an
31
+ image's own by its Query record, else 16:9; asks for it only near the view; draws
32
+ `binaryFile.show`'s answer, an image's copies as `srcset`; shows a video's first frame
33
+ with a play mark, played only in House's full-screen viewer a press opens, and plays
34
+ audio, loading only metadata; asks again when a shown file's address lapses or it is not
35
+ shown yet, until House refuses it for good; and shows its children where it cannot be
36
+ read.
39
37
  - `binaryFile.read(room, path, revision?)` answers `{ kind: 'binary', stream, byteLength,
40
- mediaType }`, the bytes a `ReadableStream` of parts of at most a mebibyte, which the
41
- browser keeps for later reads, offline too.
38
+ mediaType }`, a `ReadableStream` of parts of at most 1 MiB, kept for later reads, offline
39
+ too; `binaryFile.show` the same for a kept file, or `{ kind: 'link', url, name,
40
+ mediaType, copies }`, each copy House holds of an image as `{ url, width }` for `srcset`
41
+ and `picture`, keeping the image.
42
42
 
43
- Rooms, Query, Observe, Types, Room Draft, Save, and Reached never touch the network;
44
- Search, History, a binary file's attach and download, a file shown or read that the
43
+ Rooms, Query, Observe, Types, Room Draft, Save and Reached never touch the network;
44
+ Search, History, a file's attach and download, a file shown or read the
45
45
  browser does not keep, and Call go to the Provider through the Adapter.
46
46
  Call takes the Action names the Adapter declares, any other answering `undeclared`. A Call
47
47
  result whose effect the Provider places in a Room carries `at`, that Room and Room
48
48
  Version: `reached(room, roomVersion)` answers whether the Room's confirmed state has
49
- reached it, which the Room's next changes bring, and `reached(room, roomVersion, path)`
49
+ reached it, and `reached(room, roomVersion, path)`
50
50
  whether the held copy of that document was written later. A History revision names its
51
51
  author, `{ kind: 'user', id }` or `{ kind: 'inbox', id, name }`; History of a Path no
52
52
  document holds answers the removed document's revisions, newest first, with `deleted:
@@ -8,6 +8,7 @@ change it, build, and push. They are documentation, not enforcement.
8
8
  - [The MD Runtime API](apps/runtime-api)
9
9
  - [Local Query rules](apps/local-query)
10
10
  - [Search rules](apps/search)
11
+ - [House query rules](apps/query-house)
11
12
  - [Room Draft, Save, and outcome rules](apps/room-drafts)
12
13
  - [Content safety and launch promises](apps/content-safety)
13
14
  - [App code takes the client its host hands it and never constructs a Runtime](apps/client)