Skip to content

Commit 8fe86d2

Browse files
committed
feat(web-heartbeat-view): add standalone heartbeat package
1 parent 8d6f885 commit 8fe86d2

87 files changed

Lines changed: 11128 additions & 9 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bun.lock

Lines changed: 70 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
"packages/tmux-client",
5858
"packages/web-chat-view",
5959
"packages/web-chat-view/example",
60+
"packages/web-heartbeat-view",
61+
"packages/web-heartbeat-view/example",
6062
"packages/web-components"
6163
],
6264
"devDependencies": {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { StorybookConfig } from "@storybook/svelte-vite";
2+
3+
const config: StorybookConfig = {
4+
stories: ["../src/**/*.stories.@(ts|svelte)"],
5+
addons: ["@storybook/addon-docs", "@storybook/addon-vitest"],
6+
framework: {
7+
name: "@storybook/svelte-vite",
8+
options: {},
9+
},
10+
};
11+
12+
export default config;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { Preview } from "@storybook/svelte-vite";
2+
3+
const preview: Preview = {
4+
parameters: {
5+
controls: {
6+
matchers: {
7+
color: /(background|color)$/i,
8+
date: /Date$/i,
9+
},
10+
},
11+
viewport: {
12+
viewports: {
13+
iphone14: {
14+
name: "iPhone 14",
15+
styles: {
16+
width: "390px",
17+
height: "844px",
18+
},
19+
type: "mobile",
20+
},
21+
},
22+
defaultViewport: "iphone14",
23+
},
24+
},
25+
};
26+
27+
export default preview;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# `@agenter/web-heartbeat-view` SPEC
2+
3+
This document records durable package-level law for the Heartbeat presentation atom.
4+
5+
## 1. Package Role
6+
7+
`@agenter/web-heartbeat-view` owns the reusable Heartbeat runtime observation surface.
8+
9+
It owns:
10+
11+
- grouped Heartbeat presentation over existing runtime-store facts
12+
- parser/materialization helpers for grouped `before-call`, `call`, `compact`, and `before-call-pending` records
13+
- structured rendering for text, thinking, JSON/config facts, tool calls/results, compact cards, and footer status
14+
- explicit `readonly | configable` capability presentation
15+
- host-neutral `AgenterHeartbeatConnection` types
16+
17+
It does not own:
18+
19+
- backend Heartbeat truth or new backend endpoints
20+
- Studio route/controller state
21+
- example app routing/bootstrap
22+
- authentication or authorization policy
23+
24+
## 2. Truth Boundary
25+
26+
Heartbeat truth remains the grouped runtime/session DB projection supplied by `@agenter/client-sdk`.
27+
28+
The package must not rebuild Heartbeat truth from raw chat, raw `request_aux`, raw `heartbeat_part`, or model-call histories in the browser.
29+
30+
## 3. Capability Boundary
31+
32+
`readonly` is a frontend presentation mode that keeps the surface clean by hiding compact/config write controls.
33+
34+
`configable` may expose bottom-statusbar compact/config actions, but those actions must call explicit host callbacks.
35+
36+
Transport isolation belongs to authentication and authorization, not to the package's presentation mode.
37+
38+
## 4. Host Boundary
39+
40+
The package must not import `apps/studio`. Studio may later import this package through a thin adapter after standalone example acceptance.
41+
42+
Framework7 page wrappers are optional host conveniences; the core `HeartbeatView` remains host-neutral.
43+
44+
## 5. Verification Contract
45+
46+
High-value package verification requires:
47+
48+
- `bun run --filter '@agenter/web-heartbeat-view' typecheck`
49+
- `bun run --filter '@agenter/web-heartbeat-view' test`
50+
51+
Standalone user acceptance belongs to `packages/web-heartbeat-view/example`.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "@agenter/web-heartbeat-view-example",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite dev",
8+
"build": "vite build",
9+
"preview": "vite preview",
10+
"prepare": "svelte-kit sync || echo ''",
11+
"typecheck": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.check.json",
12+
"test": "vitest run --config ./vitest.config.ts",
13+
"start": "bun run ./scripts/start-example.ts"
14+
},
15+
"dependencies": {
16+
"@agenter/client-sdk": "workspace:*",
17+
"@agenter/web-heartbeat-view": "workspace:*",
18+
"@lucide/svelte": "^1.7.0",
19+
"dom7": "^4.0.6",
20+
"framework7": "^9.0.4",
21+
"framework7-icons": "^5.0.5",
22+
"framework7-svelte": "^9.0.4",
23+
"idb-keyval": "^6.2.2"
24+
},
25+
"devDependencies": {
26+
"@sveltejs/adapter-auto": "^7.0.1",
27+
"@sveltejs/kit": "^2.57.0",
28+
"@sveltejs/vite-plugin-svelte": "^7.0.0",
29+
"@tailwindcss/vite": "^4.2.2",
30+
"@types/bun": "latest",
31+
"@types/node": "^24",
32+
"svelte": "^5.55.2",
33+
"svelte-check": "^4.4.6",
34+
"typescript": "latest",
35+
"vite": "^8.0.7",
36+
"vitest": "^4.1.2"
37+
}
38+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import net from "node:net";
2+
3+
const preferredPort = Number(process.env.PORT ?? process.env.WEB_HEARTBEAT_VIEW_PORT ?? "4179");
4+
const host = process.env.HOST ?? "127.0.0.1";
5+
const exampleRoot = import.meta.dirname.replace(/\/scripts$/u, "");
6+
7+
const isPortAvailable = async (port: number): Promise<boolean> => {
8+
return await new Promise<boolean>((resolve) => {
9+
const server = net.createServer();
10+
server.unref();
11+
server.once("error", () => {
12+
resolve(false);
13+
});
14+
server.listen(port, host, () => {
15+
server.close(() => resolve(true));
16+
});
17+
});
18+
};
19+
20+
const pickAvailablePort = async (port: number): Promise<number> => {
21+
for (let candidate = port; candidate < port + 100; candidate += 1) {
22+
if (await isPortAvailable(candidate)) {
23+
return candidate;
24+
}
25+
}
26+
throw new Error(`unable to find an available port near ${port}`);
27+
};
28+
29+
const port = await pickAvailablePort(preferredPort);
30+
31+
const proc = Bun.spawn({
32+
cmd: ["bun", "run", "dev", "--", "--host", host, "--port", String(port)],
33+
cwd: exampleRoot,
34+
stdout: "inherit",
35+
stderr: "inherit",
36+
stdin: "inherit",
37+
});
38+
39+
console.log(`web-heartbeat-view example: http://${host}:${port}`);
40+
41+
const exitCode = await proc.exited;
42+
process.exit(exitCode);

0 commit comments

Comments
 (0)