@fungi.computer/bonsai 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @fungi.computer/bonsai
2
2
 
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Changes since 0.0.1:
8
+
9
+ - fix(agar): keep the result-row decoder out of Apps that do not present
10
+ - refactor(caps): one status bead, the gooey one, everywhere
11
+ - feat(shed): one Ghostty terminal for the light and full shells
12
+ - test(bonsai): cheaper real-git tests and a real-git vitest project
13
+ - feat(apps): describe finished Prints, Inkcap, Shed and Bonsai calls
14
+ - feat(oyster): public release 0.1.0
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies
19
+ - Updated dependencies
20
+ - Updated dependencies
21
+ - Updated dependencies
22
+ - Updated dependencies
23
+ - @fungi.computer/agar@0.2.0
24
+ - @fungi.computer/caps@0.1.0
25
+ - @fungi.computer/gills@0.1.0
26
+ - @fungi.computer/oyster@0.2.0
27
+ - @fungi.computer/puffball@0.1.0
28
+
3
29
  ## 0.0.1
4
30
 
5
31
  ### Patch Changes
@@ -1,3 +1,4 @@
1
+ import { resultRows } from "@fungi.computer/agar/method-presentation";
1
2
  import { openChangesGill, recentCommitsGill } from "./gills.js";
2
3
  import {
3
4
  pickerOptionsInput,
@@ -339,6 +340,18 @@ const definition = defineAppBackend<BonsaiMethodContext>({
339
340
  requires: [],
340
341
  input: z.strictObject({}),
341
342
  output: repoPage,
343
+ present: {
344
+ rows: resultRows,
345
+ verb: "listed",
346
+ row: ({ output }) => ({
347
+ id: "repos.list",
348
+ title: `Bonsai listed ${String(output.repos.length)} ${output.repos.length === 1 ? "repository" : "repositories"}`,
349
+ detail: output.repos
350
+ .slice(0, 5)
351
+ .map((repo) => repo.name)
352
+ .join(" · "),
353
+ }),
354
+ },
342
355
  handler: (_input, context) => ({
343
356
  repos: service.listRepos(context.bonsai),
344
357
  }),
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@fungi.computer/bonsai",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "author": "Levi <levi@technomancy.dev>",
5
5
  "type": "module",
6
6
  "description": "Bonsai: a git platform for many agents working at once, on Workers and Artifacts",
7
7
  "dependencies": {
8
- "@fungi.computer/agar": "0.1.0",
9
- "@fungi.computer/caps": "0.0.2",
8
+ "@fungi.computer/agar": "0.2.0",
9
+ "@fungi.computer/caps": "0.1.0",
10
10
  "@fungi.computer/issues": "0.0.1",
11
- "@fungi.computer/oyster": "0.1.0",
12
- "@fungi.computer/puffball": "0.0.2",
11
+ "@fungi.computer/oyster": "0.2.0",
12
+ "@fungi.computer/puffball": "0.1.0",
13
13
  "@fungi.computer/stipe": "0.0.1",
14
14
  "@pierre/diffs": "1.3.6",
15
15
  "@pierre/trees": "1.0.0-beta.6",
@@ -27,7 +27,7 @@
27
27
  "@fungi.computer/cairn": "0.0.1",
28
28
  "@fungi.computer/maosaico": "0.0.1",
29
29
  "@visx/hierarchy": "3.12.0",
30
- "@fungi.computer/gills": "0.0.1"
30
+ "@fungi.computer/gills": "0.1.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "playwright": "1.62.1",
@@ -43,12 +43,12 @@
43
43
  "vitest": "4.1.11",
44
44
  "wrangler": "4.135.0",
45
45
  "tsx": "4.23.12",
46
- "@fungi.computer/braid": "0.0.1",
47
- "@fungi.computer/gills": "0.0.1",
46
+ "@fungi.computer/braid": "0.1.0",
47
+ "@fungi.computer/gills": "0.1.0",
48
48
  "eslint": "10.9.1",
49
49
  "oxlint": "1.78.0",
50
50
  "jsdom": "29.0.1",
51
- "@fungi.computer/computer": "0.0.1"
51
+ "@fungi.computer/computer": "0.1.0"
52
52
  },
53
53
  "botanical": {
54
54
  "role": "app",
@@ -377,16 +377,17 @@ try {
377
377
  "The VM Computer fixture's sole Computer is named workspace",
378
378
  );
379
379
  const nonce = randomBytes(8).toString("hex");
380
- // Shed's light prompt is the default surface; `full` escalates one
381
- // typed line to the real PTY. Once escalated, ShellPanel unmounts
382
- // LightShellPanel — only a PTY exit (never triggered in this proof)
383
- // remounts it — so every light-mode check below runs BEFORE the
384
- // first `full`, and every later command is typed straight into the
385
- // now-visible terminal instead.
386
- const prompt = shed.getByRole("textbox", {
387
- name: "Shell command",
388
- exact: true,
389
- });
380
+ // Shed is one Ghostty terminal whose light prompt is drawn on its
381
+ // canvas; `full` escalates one typed line to the real PTY in place, and
382
+ // every later command is typed into the same canvas. Typing goes to
383
+ // Ghostty's hidden key-capture textarea, which a click on the canvas
384
+ // focuses.
385
+ const canvas = shed.locator(".terminal-host canvas");
386
+ const typeLine = async (line) => {
387
+ await canvas.click();
388
+ await page.keyboard.type(line);
389
+ await page.keyboard.press("Enter");
390
+ };
390
391
  const receivedBytes = () =>
391
392
  Buffer.concat(received.filter((frame) => Buffer.isBuffer(frame)));
392
393
  const sentBytesLength = () =>
@@ -404,14 +405,15 @@ try {
404
405
  runCommandCalls.push(request.postDataJSON());
405
406
  });
406
407
  const beforeLightInput = sentBytesLength();
407
- await prompt.click();
408
- await prompt.fill("pwd");
409
- await prompt.press("Enter");
408
+ await typeLine("pwd");
410
409
  await expect
411
- .poll(() => runCommandCalls.some((call) => call.command === "pwd"), {
412
- message: "A light command calls Shed's runCommand method",
413
- timeout: 30_000,
414
- })
410
+ .poll(
411
+ () => runCommandCalls.some((call) => call.command.endsWith("; pwd")),
412
+ {
413
+ message: "A light command calls Shed's runCommand method",
414
+ timeout: 30_000,
415
+ },
416
+ )
415
417
  .toBe(true);
416
418
  assert.equal(
417
419
  sentBytesLength(),
@@ -420,9 +422,7 @@ try {
420
422
  );
421
423
 
422
424
  const marker = `oyster-${nonce}-command`;
423
- await prompt.click();
424
- await prompt.fill(`full printf 'oyster-%s-command\\n' '${nonce}'`);
425
- await prompt.press("Enter");
425
+ await typeLine(`full printf 'oyster-%s-command\\n' '${nonce}'`);
426
426
  await expect
427
427
  .poll(() => receivedBytes().includes(marker), {
428
428
  message: "A real shell command returns through Shed's terminal",
@@ -0,0 +1,27 @@
1
+ import { expect, it } from "vitest";
2
+ import { appBackend } from "../backend/methods.js";
3
+
4
+ const repo = (name: string) => ({
5
+ name,
6
+ description: null,
7
+ parent: null,
8
+ owner: null,
9
+ createdAt: 1,
10
+ lastPushAt: null,
11
+ forks: [],
12
+ openChanges: 0,
13
+ activity: [],
14
+ });
15
+
16
+ it("reads a repository listing as a count and the first few names", () => {
17
+ const present = appBackend.methods["repos.list"]?.present;
18
+ expect(present?.({}, { repos: [repo("storyboard")] })).toEqual({
19
+ id: "repos.list",
20
+ title: "Bonsai listed 1 repository",
21
+ detail: "storyboard",
22
+ });
23
+ expect(present?.({}, { repos: [repo("a"), repo("b")] })).toMatchObject({
24
+ title: "Bonsai listed 2 repositories",
25
+ detail: "a · b",
26
+ });
27
+ });
package/vitest.config.ts CHANGED
@@ -1,5 +1,47 @@
1
1
  import { defineConfig } from "vitest/config";
2
2
 
3
+ /**
4
+ * These files drive real `git` subprocesses (init, http-backend pushes, clones,
5
+ * plumbing reads) through Agar's real-Git test kit. Each Git spawn costs a few
6
+ * milliseconds idle and tens under CI load, so a test that is about 1 s on an
7
+ * idle box crosses the 5 s default when the gate runs at several times
8
+ * oversubscription. They get their own project and timeout; the fast unit
9
+ * tests keep Vitest's default.
10
+ */
11
+ const realGit = [
12
+ "test/background-runtime.test.ts",
13
+ "test/changes.test.ts",
14
+ "test/file-edits.test.ts",
15
+ "test/files-read-copy-calls.test.ts",
16
+ "test/graph-commit-read-copy-calls.test.ts",
17
+ "test/merge-background.test.ts",
18
+ "test/merge-conflict.test.ts",
19
+ "test/merge-repro.test.ts",
20
+ "test/read-copy-composer.test.ts",
21
+ "test/read-copy-lifetimes.test.ts",
22
+ "test/repository-deletion.test.ts",
23
+ ];
24
+
3
25
  export default defineConfig({
4
- test: { include: ["test/**/*.test.ts"], environment: "node" },
26
+ test: {
27
+ projects: [
28
+ {
29
+ test: {
30
+ name: "unit",
31
+ include: ["test/**/*.test.ts"],
32
+ exclude: realGit,
33
+ environment: "node",
34
+ },
35
+ },
36
+ {
37
+ test: {
38
+ name: "real-git",
39
+ include: realGit,
40
+ environment: "node",
41
+ testTimeout: 30_000,
42
+ hookTimeout: 30_000,
43
+ },
44
+ },
45
+ ],
46
+ },
5
47
  });
@@ -14,7 +14,6 @@ import {
14
14
  } from "@fungi.computer/caps/components/data-list";
15
15
  import { InlineCode } from "@fungi.computer/caps/components/inline-code";
16
16
  import { Status } from "@fungi.computer/caps/components/status";
17
- import { StatusLed } from "@fungi.computer/caps/components/field-guide";
18
17
  import type { MergeResult, PolicyDecision } from "../shared/contract.js";
19
18
  import {
20
19
  useAgents,
@@ -569,14 +568,19 @@ export function ChangePage({ repo, number }: { repo: string; number: number }) {
569
568
  >
570
569
  <div className="min-w-0">
571
570
  <div className="flex flex-wrap items-center gap-2">
572
- <StatusLed
573
- active={check.status !== "queued"}
571
+ <Status
572
+ size="compact"
574
573
  tone={
575
574
  check.status === "passed"
576
- ? "fruiting"
575
+ ? "success"
577
576
  : check.status === "failed"
578
- ? "spore"
579
- : "mycelium"
577
+ ? "error"
578
+ : check.status === "running"
579
+ ? "info"
580
+ : "neutral"
581
+ }
582
+ motion={
583
+ check.status === "running" ? "bounce" : "none"
580
584
  }
581
585
  label={check.status}
582
586
  />