@loadbare/app 0.8.0 → 0.8.1

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.
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * loadbare-app — commands that are not the build.
4
+ *
5
+ * loadbare-app skills install [--copy] [--dir <path>]
6
+ *
7
+ * Installs the agent skills that ship in this package into `.agents/skills`
8
+ * and `.claude/skills`, or into `--dir` alone — see build/skills.ts. Nothing
9
+ * goes to stdout; every line is progress, on stderr.
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=skills-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills-cli.d.ts","sourceRoot":"","sources":["../../build/skills-cli.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG"}
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * loadbare-app — commands that are not the build.
4
+ *
5
+ * loadbare-app skills install [--copy] [--dir <path>]
6
+ *
7
+ * Installs the agent skills that ship in this package into `.agents/skills`
8
+ * and `.claude/skills`, or into `--dir` alone — see build/skills.ts. Nothing
9
+ * goes to stdout; every line is progress, on stderr.
10
+ */
11
+ import { parseArgs } from "node:util";
12
+ import path from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import { findPackageRoot } from "./package-root.js";
15
+ import { installSkills, SKILL_DESTINATIONS } from "./skills.js";
16
+ const USAGE = "usage: loadbare-app skills install [--copy] [--dir <path>]";
17
+ async function main() {
18
+ let parsed;
19
+ try {
20
+ parsed = parseArgs({
21
+ allowPositionals: true,
22
+ options: {
23
+ copy: { type: "boolean", default: false },
24
+ dir: { type: "string" },
25
+ },
26
+ });
27
+ }
28
+ catch (err) {
29
+ console.error(`loadbare-app: ${err.message}`);
30
+ console.error(USAGE);
31
+ return 1;
32
+ }
33
+ const { values, positionals } = parsed;
34
+ if (positionals.join(" ") !== "skills install") {
35
+ console.error(USAGE);
36
+ return 1;
37
+ }
38
+ // Source and compiled output sit at different depths, so the package is
39
+ // found by walking up rather than by counting.
40
+ const packageRoot = await findPackageRoot(path.dirname(fileURLToPath(import.meta.url)));
41
+ const source = path.join(packageRoot, "skills");
42
+ const destinations = values.dir ? [values.dir] : SKILL_DESTINATIONS;
43
+ const result = installSkills({
44
+ source,
45
+ destinations,
46
+ copy: values.copy,
47
+ cwd: process.cwd(),
48
+ log: (line) => console.error(line),
49
+ });
50
+ // An install with no skills in it is a packaging fault, not a user error,
51
+ // and saying where it looked is what makes that diagnosable.
52
+ if (result.names.length === 0) {
53
+ console.error(`loadbare-app: this install of @loadbare/app carries no skills (looked in ${source})`);
54
+ return 1;
55
+ }
56
+ console.error("");
57
+ if (result.copiedInstead) {
58
+ console.error("Some links could not be created and those skills were copied instead.");
59
+ console.error("A copy does not follow the package when it is upgraded — rerun this");
60
+ console.error("command after an upgrade, or use --copy everywhere and commit the result.");
61
+ console.error("");
62
+ }
63
+ if (values.copy) {
64
+ console.error("These are copies. Commit them, and rerun this command after upgrading");
65
+ console.error("@loadbare/app so the skill matches the framework.");
66
+ }
67
+ else {
68
+ console.error("These are links into node_modules. To keep them out of git:");
69
+ console.error("");
70
+ for (const destination of destinations) {
71
+ for (const name of result.names) {
72
+ console.error(` ${path.join(destination, name)}`);
73
+ }
74
+ }
75
+ }
76
+ console.error("");
77
+ console.error("Start your agent from this directory and it will find them.");
78
+ return result.refused > 0 ? 1 : 0;
79
+ }
80
+ process.exitCode = await main();
81
+ //# sourceMappingURL=skills-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills-cli.js","sourceRoot":"","sources":["../../build/skills-cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * loadbare-app — commands that are not the build.\n *\n * loadbare-app skills install [--copy] [--dir <path>]\n *\n * Installs the agent skills that ship in this package into `.agents/skills`\n * and `.claude/skills`, or into `--dir` alone — see build/skills.ts. Nothing\n * goes to stdout; every line is progress, on stderr.\n */\n\nimport { parseArgs } from \"node:util\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { findPackageRoot } from \"./package-root.js\";\nimport { installSkills, SKILL_DESTINATIONS } from \"./skills.js\";\n\nconst USAGE = \"usage: loadbare-app skills install [--copy] [--dir <path>]\";\n\nasync function main(): Promise<number> {\n let parsed;\n try {\n parsed = parseArgs({\n allowPositionals: true,\n options: {\n copy: { type: \"boolean\", default: false },\n dir: { type: \"string\" },\n },\n });\n } catch (err) {\n console.error(`loadbare-app: ${(err as Error).message}`);\n console.error(USAGE);\n return 1;\n }\n\n const { values, positionals } = parsed;\n if (positionals.join(\" \") !== \"skills install\") {\n console.error(USAGE);\n return 1;\n }\n\n // Source and compiled output sit at different depths, so the package is\n // found by walking up rather than by counting.\n const packageRoot = await findPackageRoot(\n path.dirname(fileURLToPath(import.meta.url)),\n );\n const source = path.join(packageRoot, \"skills\");\n const destinations = values.dir ? [values.dir] : SKILL_DESTINATIONS;\n\n const result = installSkills({\n source,\n destinations,\n copy: values.copy!,\n cwd: process.cwd(),\n log: (line) => console.error(line),\n });\n\n // An install with no skills in it is a packaging fault, not a user error,\n // and saying where it looked is what makes that diagnosable.\n if (result.names.length === 0) {\n console.error(\n `loadbare-app: this install of @loadbare/app carries no skills (looked in ${source})`,\n );\n return 1;\n }\n\n console.error(\"\");\n if (result.copiedInstead) {\n console.error(\n \"Some links could not be created and those skills were copied instead.\",\n );\n console.error(\n \"A copy does not follow the package when it is upgraded — rerun this\",\n );\n console.error(\n \"command after an upgrade, or use --copy everywhere and commit the result.\",\n );\n console.error(\"\");\n }\n\n if (values.copy) {\n console.error(\n \"These are copies. Commit them, and rerun this command after upgrading\",\n );\n console.error(\"@loadbare/app so the skill matches the framework.\");\n } else {\n console.error(\n \"These are links into node_modules. To keep them out of git:\",\n );\n console.error(\"\");\n for (const destination of destinations) {\n for (const name of result.names) {\n console.error(` ${path.join(destination, name)}`);\n }\n }\n }\n\n console.error(\"\");\n console.error(\"Start your agent from this directory and it will find them.\");\n\n return result.refused > 0 ? 1 : 0;\n}\n\nprocess.exitCode = await main();\n"]}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Put the agent skills that ship in this package where a coding agent will
3
+ * find them.
4
+ *
5
+ * The same command as `loadbare-db skills install`, and deliberately a copy
6
+ * of it rather than a shared module: `@loadbare/db` shares no code with this
7
+ * package, and this package imports nothing from the repository.
8
+ *
9
+ * A symlink by default, because the point is that the skill tracks the
10
+ * dependency: `npm install @loadbare/app@next` and the skill moves with it.
11
+ * `copy` is for a team that would rather commit the text than have every
12
+ * clone depend on `node_modules` being populated.
13
+ *
14
+ * Nothing is done to `.gitignore`. Whether these paths are committed is a
15
+ * decision about the project, and the command that installs a file is not the
16
+ * one to make it.
17
+ */
18
+ /**
19
+ * Where a coding agent looks for skills.
20
+ *
21
+ * `.agents/skills` is the vendor-neutral location, read by Codex, Cursor and
22
+ * Copilot; `.claude/skills` is Claude Code's, and Cursor reads it too. Both
23
+ * are written rather than guessing from what happens to be configured.
24
+ */
25
+ export declare const SKILL_DESTINATIONS: string[];
26
+ export interface InstallOptions {
27
+ /** The directory holding one subdirectory per skill. */
28
+ source: string;
29
+ /** The directories to install into. */
30
+ destinations: string[];
31
+ /** Copy rather than link. */
32
+ copy: boolean;
33
+ /** The project root; a link to a package inside it is relative. */
34
+ cwd: string;
35
+ /** Where progress goes, one line at a time. */
36
+ log: (line: string) => void;
37
+ }
38
+ export interface InstallResult {
39
+ names: string[];
40
+ refused: number;
41
+ /** A link was refused by the platform and a copy made in its place. */
42
+ copiedInstead: boolean;
43
+ }
44
+ /** The skills under `source`: each directory holding a `SKILL.md`. */
45
+ export declare function shippedSkills(source: string): string[];
46
+ export declare function installSkills(options: InstallOptions): InstallResult;
47
+ //# sourceMappingURL=skills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../build/skills.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAcH;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,UAAuC,CAAC;AAEvE,MAAM,WAAW,cAAc;IAC7B,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,6BAA6B;IAC7B,IAAI,EAAE,OAAO,CAAC;IACd,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAC;IACZ,+CAA+C;IAC/C,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,aAAa,EAAE,OAAO,CAAC;CACxB;AAwCD,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAUtD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,CAsDpE"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Put the agent skills that ship in this package where a coding agent will
3
+ * find them.
4
+ *
5
+ * The same command as `loadbare-db skills install`, and deliberately a copy
6
+ * of it rather than a shared module: `@loadbare/db` shares no code with this
7
+ * package, and this package imports nothing from the repository.
8
+ *
9
+ * A symlink by default, because the point is that the skill tracks the
10
+ * dependency: `npm install @loadbare/app@next` and the skill moves with it.
11
+ * `copy` is for a team that would rather commit the text than have every
12
+ * clone depend on `node_modules` being populated.
13
+ *
14
+ * Nothing is done to `.gitignore`. Whether these paths are committed is a
15
+ * decision about the project, and the command that installs a file is not the
16
+ * one to make it.
17
+ */
18
+ import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmSync, symlinkSync, } from "node:fs";
19
+ import path from "node:path";
20
+ /**
21
+ * Where a coding agent looks for skills.
22
+ *
23
+ * `.agents/skills` is the vendor-neutral location, read by Codex, Cursor and
24
+ * Copilot; `.claude/skills` is Claude Code's, and Cursor reads it too. Both
25
+ * are written rather than guessing from what happens to be configured.
26
+ */
27
+ export const SKILL_DESTINATIONS = [".agents/skills", ".claude/skills"];
28
+ /** `absent` covers a dangling symlink, which `existsSync` reports as missing. */
29
+ function entryKind(p) {
30
+ let stat;
31
+ try {
32
+ stat = lstatSync(p);
33
+ }
34
+ catch {
35
+ return "absent";
36
+ }
37
+ if (stat.isSymbolicLink())
38
+ return "symlink";
39
+ if (stat.isDirectory())
40
+ return "directory";
41
+ return "other";
42
+ }
43
+ /** The `name:` a skill directory declares, or null if there is no skill there. */
44
+ function declaredSkillName(dir) {
45
+ try {
46
+ const match = /^name:\s*(\S+)\s*$/m.exec(readFileSync(path.join(dir, "SKILL.md"), "utf-8"));
47
+ return match?.[1] ?? null;
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ /**
54
+ * A link into `node_modules` is relative, so the project stays movable and the
55
+ * link means the same thing to everyone who checks it out. A globally
56
+ * installed package gets an absolute path, since a relative one breaks the
57
+ * moment the project moves.
58
+ */
59
+ function linkTarget(from, to, cwd) {
60
+ const inside = path.resolve(from).startsWith(path.resolve(cwd) + path.sep);
61
+ return inside
62
+ ? path.relative(path.dirname(path.resolve(cwd, to)), path.resolve(from))
63
+ : path.resolve(from);
64
+ }
65
+ /** The skills under `source`: each directory holding a `SKILL.md`. */
66
+ export function shippedSkills(source) {
67
+ if (!existsSync(source))
68
+ return [];
69
+ return readdirSync(source, { withFileTypes: true })
70
+ .filter((entry) => entry.isDirectory() &&
71
+ existsSync(path.join(source, entry.name, "SKILL.md")))
72
+ .map((entry) => entry.name)
73
+ .sort();
74
+ }
75
+ export function installSkills(options) {
76
+ const { source, destinations, copy, cwd, log } = options;
77
+ const names = shippedSkills(source);
78
+ let refused = 0;
79
+ let copiedInstead = false;
80
+ for (const destination of destinations) {
81
+ for (const name of names) {
82
+ const from = path.join(source, name);
83
+ const to = path.join(destination, name);
84
+ const at = path.resolve(cwd, to);
85
+ const kind = entryKind(at);
86
+ // A directory this command wrote says so on its own front matter, and
87
+ // is replaced. Anything else at that path belongs to someone, and a
88
+ // skill install is not permitted to be the thing that lost it.
89
+ if (kind === "directory" && declaredSkillName(at) !== name) {
90
+ log(` refused ${to} — a directory is already there and is not the ${name} skill`);
91
+ refused++;
92
+ continue;
93
+ }
94
+ if (kind === "other") {
95
+ log(` refused ${to} — a file is already there`);
96
+ refused++;
97
+ continue;
98
+ }
99
+ mkdirSync(path.resolve(cwd, destination), { recursive: true });
100
+ rmSync(at, { recursive: true, force: true });
101
+ if (copy) {
102
+ cpSync(from, at, { recursive: true });
103
+ log(` copied ${to}`);
104
+ continue;
105
+ }
106
+ try {
107
+ symlinkSync(linkTarget(from, to, cwd), at, "dir");
108
+ log(` linked ${to}`);
109
+ }
110
+ catch (error) {
111
+ // Windows refuses a symlink to an unprivileged process unless
112
+ // developer mode is on. A copy is the same skill, so the install
113
+ // succeeds and the difference is reported rather than raised.
114
+ if (error.code !== "EPERM")
115
+ throw error;
116
+ cpSync(from, at, { recursive: true });
117
+ copiedInstead = true;
118
+ log(` copied ${to}`);
119
+ }
120
+ }
121
+ }
122
+ return { names, refused, copiedInstead };
123
+ }
124
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../../build/skills.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Put the agent skills that ship in this package where a coding agent will\n * find them.\n *\n * The same command as `loadbare-db skills install`, and deliberately a copy\n * of it rather than a shared module: `@loadbare/db` shares no code with this\n * package, and this package imports nothing from the repository.\n *\n * A symlink by default, because the point is that the skill tracks the\n * dependency: `npm install @loadbare/app@next` and the skill moves with it.\n * `copy` is for a team that would rather commit the text than have every\n * clone depend on `node_modules` being populated.\n *\n * Nothing is done to `.gitignore`. Whether these paths are committed is a\n * decision about the project, and the command that installs a file is not the\n * one to make it.\n */\n\nimport {\n cpSync,\n existsSync,\n lstatSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n rmSync,\n symlinkSync,\n} from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Where a coding agent looks for skills.\n *\n * `.agents/skills` is the vendor-neutral location, read by Codex, Cursor and\n * Copilot; `.claude/skills` is Claude Code's, and Cursor reads it too. Both\n * are written rather than guessing from what happens to be configured.\n */\nexport const SKILL_DESTINATIONS = [\".agents/skills\", \".claude/skills\"];\n\nexport interface InstallOptions {\n /** The directory holding one subdirectory per skill. */\n source: string;\n /** The directories to install into. */\n destinations: string[];\n /** Copy rather than link. */\n copy: boolean;\n /** The project root; a link to a package inside it is relative. */\n cwd: string;\n /** Where progress goes, one line at a time. */\n log: (line: string) => void;\n}\n\nexport interface InstallResult {\n names: string[];\n refused: number;\n /** A link was refused by the platform and a copy made in its place. */\n copiedInstead: boolean;\n}\n\n/** `absent` covers a dangling symlink, which `existsSync` reports as missing. */\nfunction entryKind(p: string): \"absent\" | \"symlink\" | \"directory\" | \"other\" {\n let stat;\n try {\n stat = lstatSync(p);\n } catch {\n return \"absent\";\n }\n if (stat.isSymbolicLink()) return \"symlink\";\n if (stat.isDirectory()) return \"directory\";\n return \"other\";\n}\n\n/** The `name:` a skill directory declares, or null if there is no skill there. */\nfunction declaredSkillName(dir: string): string | null {\n try {\n const match = /^name:\\s*(\\S+)\\s*$/m.exec(\n readFileSync(path.join(dir, \"SKILL.md\"), \"utf-8\"),\n );\n return match?.[1] ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * A link into `node_modules` is relative, so the project stays movable and the\n * link means the same thing to everyone who checks it out. A globally\n * installed package gets an absolute path, since a relative one breaks the\n * moment the project moves.\n */\nfunction linkTarget(from: string, to: string, cwd: string): string {\n const inside = path.resolve(from).startsWith(path.resolve(cwd) + path.sep);\n return inside\n ? path.relative(path.dirname(path.resolve(cwd, to)), path.resolve(from))\n : path.resolve(from);\n}\n\n/** The skills under `source`: each directory holding a `SKILL.md`. */\nexport function shippedSkills(source: string): string[] {\n if (!existsSync(source)) return [];\n return readdirSync(source, { withFileTypes: true })\n .filter(\n (entry) =>\n entry.isDirectory() &&\n existsSync(path.join(source, entry.name, \"SKILL.md\")),\n )\n .map((entry) => entry.name)\n .sort();\n}\n\nexport function installSkills(options: InstallOptions): InstallResult {\n const { source, destinations, copy, cwd, log } = options;\n const names = shippedSkills(source);\n let refused = 0;\n let copiedInstead = false;\n\n for (const destination of destinations) {\n for (const name of names) {\n const from = path.join(source, name);\n const to = path.join(destination, name);\n const at = path.resolve(cwd, to);\n const kind = entryKind(at);\n\n // A directory this command wrote says so on its own front matter, and\n // is replaced. Anything else at that path belongs to someone, and a\n // skill install is not permitted to be the thing that lost it.\n if (kind === \"directory\" && declaredSkillName(at) !== name) {\n log(\n ` refused ${to} — a directory is already there and is not the ${name} skill`,\n );\n refused++;\n continue;\n }\n if (kind === \"other\") {\n log(` refused ${to} — a file is already there`);\n refused++;\n continue;\n }\n\n mkdirSync(path.resolve(cwd, destination), { recursive: true });\n rmSync(at, { recursive: true, force: true });\n\n if (copy) {\n cpSync(from, at, { recursive: true });\n log(` copied ${to}`);\n continue;\n }\n\n try {\n symlinkSync(linkTarget(from, to, cwd), at, \"dir\");\n log(` linked ${to}`);\n } catch (error) {\n // Windows refuses a symlink to an unprivileged process unless\n // developer mode is on. A copy is the same skill, so the install\n // succeeds and the difference is reported rather than raised.\n if ((error as NodeJS.ErrnoException).code !== \"EPERM\") throw error;\n cpSync(from, at, { recursive: true });\n copiedInstead = true;\n log(` copied ${to}`);\n }\n }\n }\n\n return { names, refused, copiedInstead };\n}\n"]}
@@ -19,8 +19,8 @@ npm install @loadbare/widgets
19
19
  export default ["@loadbare/widgets"];
20
20
  ```
21
21
 
22
- See [Using Widget Libraries](../tutorials/090-using-widget-libraries.md) for
23
- what listing a package does, and [The Builder](./builder.md#where-the-builder-looks)
22
+ See [Widgets from packages](./builder.md#widgets-from-packages) for what
23
+ listing a package does, and [The Builder](./builder.md#where-the-builder-looks)
24
24
  for where a listed package sits in the cascade.
25
25
 
26
26
  ## `lb-input`
package/package.json CHANGED
@@ -1,18 +1,21 @@
1
1
  {
2
2
  "name": "@loadbare/app",
3
3
  "description": "High performance web app framework for server-bound applications",
4
- "version": "0.8.0",
4
+ "version": "0.8.1",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",
8
8
  "docs",
9
+ "skills/loadbare-app/SKILL.md",
10
+ "skills/loadbare-app/references",
9
11
  "README.md"
10
12
  ],
11
13
  "publishConfig": {
12
14
  "access": "public"
13
15
  },
14
16
  "bin": {
15
- "loadbare-app-build": "dist/build/cli.js"
17
+ "loadbare-app-build": "dist/build/cli.js",
18
+ "loadbare-app": "dist/build/skills-cli.js"
16
19
  },
17
20
  "exports": {
18
21
  ".": "./dist/hub/lb-hub.browser.js",
@@ -31,8 +34,9 @@
31
34
  "release:major": "node ../../scripts/bump-release.mjs major",
32
35
  "prebuild": "node --eval \"fs.rmSync('dist',{recursive:true,force:true})\" --input-type=module",
33
36
  "build:server": "tsc --project tsconfig.build.json",
34
- "build": "npm run build:server",
35
- "postbuild": "node --eval \"fs.chmodSync('dist/build/cli.js', 0o755)\" --input-type=module && node ../../scripts/check-dist-imports.mjs",
37
+ "build:skills": "node scripts/build-skills.mjs",
38
+ "build": "npm run build:server && npm run build:skills",
39
+ "postbuild": "node --eval \"for (const f of ['dist/build/cli.js','dist/build/skills-cli.js']) fs.chmodSync(f, 0o755)\" --input-type=module && node ../../scripts/check-dist-imports.mjs",
36
40
  "test": "tsx --test \"tests/**/*.test.ts\"",
37
41
  "typecheck": "tsc --noEmit",
38
42
  "format": "prettier --write .",
@@ -0,0 +1,258 @@
1
+ ---
2
+ name: loadbare-app
3
+ description: Build server-bound web applications with Loadbare/app. Covers the file conventions the builder finds by name (`chrome.html`, `*.page.html`, `*.queries.ts`, `*.requests.ts`, `imports.ts`, `<tag>.html`, `<tag>.browser.ts`); the `lb-*` attribute vocabulary that binds HTML to server data and sends requests (`lb-list`, `lb-row`, `lb-cell`, `lb-key`, `lb-show`, `lb-action`); build-time widget expansion with `exp-*` parameters, `lb-slot` and `lb-template`; custom element code; the Express wiring with `hubRoutes`; and the `@loadbare/widgets` library. Use whenever a task involves `@loadbare/app`, `@loadbare/widgets`, `loadbare-app-build`, an `lb-` attribute, or a page, query, request or widget file in a Loadbare application. The model is not React, htmx or REST, and cannot be inferred from them.
4
+ license: Apache-2.0
5
+ metadata:
6
+ package: "@loadbare/app"
7
+ homepage: https://gitlab.com/kendowns/loadbare
8
+ ---
9
+
10
+ # Working with Loadbare/app
11
+
12
+ Loadbare/app builds a whole application into one HTML document, one client
13
+ script and one stylesheet. The server answers named queries with rows and
14
+ performs named requests; the browser lands those rows on elements that name
15
+ them. No component renders anything, and no page fetches anything.
16
+
17
+ The wire is relational. A name answers with one row or a set of rows, a
18
+ row holds cells, and a cell holds one value. Hold on to that: most wrong
19
+ designs come from sending a shape a relational answer cannot have.
20
+
21
+ ## Build every change before handing it over
22
+
23
+ `loadbare-app-build` is the checker. It expands every page and widget and
24
+ refuses what it cannot ship, naming the tag and the file:
25
+
26
+ ```bash
27
+ loadbare-app-build --src src --out dist
28
+ ```
29
+
30
+ It exits non-zero on the first failure and prints
31
+ `loadbare-app-build: <message>`. A clean run prints the files it wrote and
32
+ the number of custom elements it bundled.
33
+
34
+ Type-check the server side as well:
35
+
36
+ ```bash
37
+ tsc --noEmit
38
+ ```
39
+
40
+ `dist/pages.ts` imports page files with their `.ts` extensions, so the
41
+ application's `tsconfig.json` needs `allowImportingTsExtensions`.
42
+
43
+ **Write, build, fix, repeat until it is clean.** Then start the server:
44
+ `createHub` refuses a query that answers with the wrong shape and a page
45
+ that declares a reserved name, and those refusals only appear at run time.
46
+ Restart the server after adding or changing a `.queries.ts` or
47
+ `.requests.ts` file.
48
+
49
+ ## The shape of an application
50
+
51
+ The builder classifies files by name, never by directory:
52
+
53
+ ```
54
+ src/
55
+ chrome.html exactly one; the document every page lands in
56
+ imports.ts at most one; the widget packages to scan
57
+ 00-reset.css any .css anywhere, concatenated
58
+ pages/
59
+ index.page.html the page for /
60
+ members.page.html the page for /members
61
+ members.queries.ts what members displays
62
+ members.requests.ts what members may be asked to do
63
+ widgets/
64
+ note-card.html the markup <note-card> expands into
65
+ visit-count.browser.ts the class <visit-count> registers
66
+ server.ts
67
+ ```
68
+
69
+ The chrome carries `<lb-hub>` inside `<body>`, an empty `<main>` inside the
70
+ hub, and `<script src="/client.js" defer>`. Everything a user touches sits
71
+ inside `<lb-hub>`.
72
+
73
+ A page is an HTML fragment that lands in `<main>`:
74
+
75
+ ```html
76
+ <!-- src/pages/members.page.html -->
77
+ <p lb-row="dues">Collected this year: <span lb-cell="total"></span></p>
78
+
79
+ <section lb-list="roster">
80
+ <form lb-action="lb-row-insert">
81
+ <input lb-cell="name" />
82
+ <button type="submit">Add</button>
83
+ </form>
84
+ <ul>
85
+ <template lb-key="id">
86
+ <li>
87
+ <span lb-cell="name"></span>
88
+ <button lb-action="lb-row-delete">Remove</button>
89
+ </li>
90
+ </template>
91
+ </ul>
92
+ </section>
93
+ ```
94
+
95
+ ```ts
96
+ // src/pages/members.queries.ts
97
+ import { list, row, type Queries } from "@loadbare/app/server";
98
+
99
+ export const queries: Queries = {
100
+ dues: row((ctx) => ctx.db.duesTotal()),
101
+ roster: list((ctx) => ctx.db.members()),
102
+ };
103
+ ```
104
+
105
+ ```ts
106
+ // src/pages/members.requests.ts
107
+ import { patch, type Requests } from "@loadbare/app/server";
108
+
109
+ export const requests: Requests = {
110
+ crud: {
111
+ roster: {
112
+ rowInsert: {
113
+ run: async (ctx, { values }) => ({
114
+ roster: patch({ rows: [await ctx.db.addMember(values)] }),
115
+ }),
116
+ refresh: ["dues"],
117
+ },
118
+ rowDelete: {
119
+ run: async (ctx, { key }) => {
120
+ await ctx.db.removeMember(key);
121
+ return { roster: patch({ drop: [key] }) };
122
+ },
123
+ refresh: ["dues"],
124
+ },
125
+ },
126
+ },
127
+ };
128
+ ```
129
+
130
+ ## Where front-end knowledge misleads
131
+
132
+ Re-read this list when a build fails or a design will not fit. Each item is
133
+ a place a reasonable instinct from React, Vue, htmx or REST produces an
134
+ error or a dead end.
135
+
136
+ **There is no template language.** No `{#if}`, no `v-for`, no `map()`. A
137
+ list is an element carrying `lb-list` with a `<template lb-key="...">`
138
+ inside it, and the hub clones the template once per row. A condition is
139
+ `lb-show="<column>"`: write every possibility into the page and let a column
140
+ decide which is present. The column must be a boolean or null; the string
141
+ `"false"` counts as present.
142
+
143
+ **`{{placeholder}}` is build time only.** It reads an `exp-` attribute on
144
+ the tag, never data. Write it as an entire attribute value or an entire
145
+ text node. Name parameters in lowercase with hyphens: HTML lowercases
146
+ `exp-inputClass` before expansion sees it.
147
+
148
+ **Scope comes from ancestry, not props.** An `lb-cell` binds to the row on
149
+ its nearest ancestor carrying `lb-row` or a live list row. An element
150
+ outside every scope displays nothing. A value of `lb-cell` is a column name.
151
+
152
+ **One name, one cardinality.** Declare every query with `row()` or
153
+ `list()`. A page that needs the same data as a row and as a set declares
154
+ two queries.
155
+
156
+ **A cell never holds rows.** Master-detail is a row and a list under two
157
+ names. Many masters with their details is one list of joined rows, grouped
158
+ for display by a widget's `lbPlaceRow`. A list nested inside another list's
159
+ rows receives the same rows in every outer row; use it for a picker, never
160
+ for per-row detail.
161
+
162
+ **A URL names a page, never a resource.** Do not design `/accounts/42`. A
163
+ row is addressed by `list` and `key`, taken from where the element sits.
164
+ Queries take no arguments from the browser; which record a page shows is
165
+ server state reached through `ctx`.
166
+
167
+ **There are no endpoints to write.** `hubRoutes(hub, contextFor)` is the
168
+ whole data channel. Declare every action under `actions` and every
169
+ permitted operation under `crud`; anything undeclared is refused.
170
+
171
+ **All three CRUD operations are list operations.** Each needs a key, and a
172
+ key exists only on a live row inside `lb-list`. An `lb-row` scope is
173
+ read-only; give a writable single row a list that answers with one row.
174
+
175
+ **Write `rowUpdate` as a partial update.** A form sends every cell it
176
+ holds; a widget carrying `lb-cell` and `lb-action="lb-row-update"` sends its
177
+ one cell. Set the columns `values` names, leave the rest alone, and check
178
+ the names against the columns the page may edit.
179
+
180
+ **Put `lb-row-insert` and `lb-row-update` on a `<form>` or a button.** The
181
+ hub gathers the nearest `<form>`, `<tr>` or live row. Cells in a bare
182
+ `<div>` belong to no row and the request is refused. A native `<input>`
183
+ carrying `lb-row-update` is refused; commit one cell with a widget such as
184
+ `<lb-input>`. Inside a form, leave `lb-action` off the widgets.
185
+
186
+ **Return a patch when the change has a known extent.** `patch({ rows })`
187
+ for rows added or edited, `patch({ drop })` for keys removed, with
188
+ `refresh: []`. List a query in `refresh` only when its membership or order
189
+ changed in a way the operation cannot name.
190
+
191
+ **Format values in the query.** The hub does no type conversion. What a
192
+ number, date or null looks like is decided on the server.
193
+
194
+ **Checkboxes, radio buttons and file inputs are not bound.** They receive
195
+ no value and are not gathered. This is an open item, not a mistake in the
196
+ page.
197
+
198
+ **`lb-` belongs to Loadbare.** Invent no `lb-` attribute, and name no
199
+ query or action with the prefix. Import attribute names in widget code from
200
+ `@loadbare/app/constants`; never write them as string literals.
201
+
202
+ **Name a widget script `<tag>.browser.ts`.** A plain `<tag>.ts` stays on
203
+ the server and the tag goes unregistered.
204
+
205
+ **Light DOM, global CSS.** No shadow root, no scoping. Style empty lists
206
+ with `[lb-row-count="0"]`, pending requests with `[lb-pending]`, and failed
207
+ ones with `[lb-error]`.
208
+
209
+ **Host at the origin root.** The hub reaches its route by absolute path, so
210
+ a subpath such as `example.com/myapp/` does not work.
211
+
212
+ ## Widgets
213
+
214
+ A widget is `<tag>.html`, `<tag>.browser.ts`, or both, and a tag with
215
+ neither is a build error. The HTML is expanded at build time into the tag's
216
+ children; the class is an ordinary custom element with no base class.
217
+
218
+ Reach for a widget only when plain HTML cannot do the job. A list, a form
219
+ and a condition need none. A widget exists to wrap a control that decides
220
+ its own moment to send (`<lb-input>` on `change`), or to place and scaffold
221
+ rows (`lbPlaceRow`, `lbRowsLanded`).
222
+
223
+ Before writing one, check `@loadbare/widgets`: `lb-input`, `lb-select`,
224
+ `lb-options`, `lb-table`, `lb-picker`, `lb-unknown-page`. Install it and
225
+ list it in `src/imports.ts`:
226
+
227
+ ```ts
228
+ export default ["@loadbare/widgets"];
229
+ ```
230
+
231
+ The application's own definition of a tag overrides a package's.
232
+
233
+ ## Reference material
234
+
235
+ Read these when the summary above does not settle the question. They ship
236
+ with the package, so no network is needed.
237
+
238
+ - [`references/overview.md`](references/overview.md) — the map of the
239
+ reference, by part of the application.
240
+ - [`references/data-binding.md`](references/data-binding.md) — every `lb-`
241
+ attribute, requests, forms, conditions and request state. Start here for
242
+ anything in a page.
243
+ - [`references/page-files.md`](references/page-files.md) — queries,
244
+ `onPageEnter`, actions, CRUD, refresh and patch.
245
+ - [`references/custom-elements.md`](references/custom-elements.md) —
246
+ expansion, parameters, slots, destinations, and widget code.
247
+ - [`references/chrome.md`](references/chrome.md) — the chrome,
248
+ navigation and `lb-navigation`.
249
+ - [`references/server.md`](references/server.md) — the Express server and
250
+ the request context.
251
+ - [`references/builder.md`](references/builder.md) — the builder's options,
252
+ what it reads, and where it looks.
253
+ - [`references/css.md`](references/css.md) — how stylesheets are ordered.
254
+ - [`references/widgets.md`](references/widgets.md) — the basic widget
255
+ library.
256
+ - [`references/TECHREF-1.0.md`](references/TECHREF-1.0.md) — the reserved
257
+ names, and the open items that block 1.0. Read it before designing around
258
+ something the other references do not mention.