create-bridgething 0.12.0 → 0.12.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.
- package/dist/index.mjs +439 -170
- package/package.json +13 -4
- package/template/_claude/skills/bridgething/reference/develop.md +5 -4
- package/template/_claude/skills/bridgething/reference/sdk.md +13 -0
- package/template/package.json +1 -1
- package/template/settings/main.tsx +14 -13
- package/template/src/App.tsx +1 -1
- package/template/tsconfig.json +1 -1
- package/template/vite.settings.config.ts +3 -3
- package/template-overlay/vite.overlay.config.ts +1 -1
- package/template-source/CLAUDE.md +104 -0
- package/template-source/README.md +57 -0
- package/template-source/_bunversion +1 -0
- package/template-source/_github/workflows/ci.yml +28 -0
- package/template-source/_github/workflows/publish.yml +33 -0
- package/template-source/_gitignore +7 -0
- package/template-source/_prettierrc.js +10 -0
- package/template-source/apps/.gitkeep +0 -0
- package/template-source/package.json +27 -0
- package/template-source/source.json +8 -0
- package/template-versions.json +6 -0
- package/template/CLAUDE.md +0 -71
- package/template/README.md +0 -57
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-bridgething",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Scaffold a
|
|
5
|
+
"description": "Scaffold a bridgething app source: webapps for the Spotify Car Thing, published as a catalog.v1 on GitHub Pages.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Joey Eamigh <55670930+JoeyEamigh@users.noreply.github.com> (https://github.com/JoeyEamigh)",
|
|
8
8
|
"homepage": "https://bridgething.com",
|
|
@@ -14,7 +14,14 @@
|
|
|
14
14
|
"bugs": {
|
|
15
15
|
"url": "https://github.com/JoeyEamigh/bridgething/issues"
|
|
16
16
|
},
|
|
17
|
-
"keywords": [
|
|
17
|
+
"keywords": [
|
|
18
|
+
"bridgething",
|
|
19
|
+
"spotify",
|
|
20
|
+
"car-thing",
|
|
21
|
+
"superbird",
|
|
22
|
+
"create",
|
|
23
|
+
"scaffold"
|
|
24
|
+
],
|
|
18
25
|
"publishConfig": {
|
|
19
26
|
"access": "public",
|
|
20
27
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -24,7 +31,9 @@
|
|
|
24
31
|
},
|
|
25
32
|
"files": [
|
|
26
33
|
"dist",
|
|
34
|
+
"template-versions.json",
|
|
27
35
|
"template",
|
|
36
|
+
"template-source",
|
|
28
37
|
"template-extension",
|
|
29
38
|
"template-launcher",
|
|
30
39
|
"template-overlay"
|
|
@@ -37,7 +46,7 @@
|
|
|
37
46
|
"test": "bun test"
|
|
38
47
|
},
|
|
39
48
|
"devDependencies": {
|
|
40
|
-
"@bridgething/extension": "0.12.
|
|
49
|
+
"@bridgething/extension": "0.12.1",
|
|
41
50
|
"@msgpack/msgpack": "^3.1.3",
|
|
42
51
|
"@types/bun": "^1.4.0",
|
|
43
52
|
"@types/node": "^26.1.1",
|
|
@@ -72,15 +72,16 @@ scrolling for the rotary's horizontal wheel.
|
|
|
72
72
|
CDP input goes into the page, so the launcher gesture and browser-nav side
|
|
73
73
|
effects stay out of the way.
|
|
74
74
|
|
|
75
|
-
Get the app on the kiosk first with `bun run dev:device` or `bun run push`.
|
|
76
|
-
|
|
75
|
+
Get the app on the kiosk first with `bun run dev:device` or `bun run push`.
|
|
76
|
+
Chromium listens on loopback, but the image proxies it, so CDP answers on
|
|
77
|
+
`bridgething.local:9222` over USB:
|
|
77
78
|
|
|
78
79
|
```bash
|
|
79
80
|
curl -s http://bridgething.local:9222/json/version >/dev/null && echo cdp-up
|
|
80
81
|
```
|
|
81
82
|
|
|
82
|
-
Use the `webSocketDebuggerUrl` exactly as returned. It comes back as an IP
|
|
83
|
-
than the hostname you asked for, and that address is the correct one.
|
|
83
|
+
Use the `webSocketDebuggerUrl` exactly as returned. It comes back as an IP
|
|
84
|
+
rather than the hostname you asked for, and that address is the correct one.
|
|
84
85
|
|
|
85
86
|
Playwright's `connectOverCDP` never finishes the websocket upgrade against the
|
|
86
87
|
device's embedded chromium, so drive it with a raw CDP websocket. Connect to the
|
|
@@ -116,6 +116,19 @@ fetch and XHR. WebSocket APIs work. Plain HTTP APIs work when the server sends
|
|
|
116
116
|
permissive CORS headers, since requests arrive with `Origin: null`. For a
|
|
117
117
|
CORS-strict HTTP-only service, fetch through `client.net` instead.
|
|
118
118
|
|
|
119
|
+
Write it in react like the rest of the app. `react` and `react-dom/client`
|
|
120
|
+
imports, `className`, `htmlFor`, react's event types. The build runs it on
|
|
121
|
+
preact through `@preact/preset-vite`, which is most of what keeps the page
|
|
122
|
+
small, so keep the react-flavoured source and leave the preset alone. One
|
|
123
|
+
tsconfig covers `src`, `settings` and `extension`, so `bun run typecheck`
|
|
124
|
+
checks all three.
|
|
125
|
+
|
|
126
|
+
Keep the page small. It builds to one self-contained HTML file, and the device
|
|
127
|
+
refuses to install a bundle whose declared settings page is over 1 MiB, failing
|
|
128
|
+
the whole install with an invalid-manifest error. The build warns past 200 KiB
|
|
129
|
+
and again past 500 KiB. A heavy dependency in `settings/` is the usual way
|
|
130
|
+
there.
|
|
131
|
+
|
|
119
132
|
`client.net` tunnels HTTP, websockets, and a SOCKS proxy through the phone. The
|
|
120
133
|
SOCKS proxy needs `"net.proxy"` in the manifest's `permissions`. Read the
|
|
121
134
|
`NetSurface` block in the `.d.ts` for the request shapes.
|
package/template/package.json
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
"dev": "vite",
|
|
8
8
|
"dev:device": "vite --mode device",
|
|
9
9
|
"build": "vite build && vite build -c vite.settings.config.ts",
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
10
11
|
"preview": "vite preview",
|
|
11
12
|
"push": "bun scripts/push.ts",
|
|
12
13
|
"share": "bun scripts/share.ts",
|
|
13
14
|
"update": "bunx @bridgething/updater"
|
|
14
15
|
},
|
|
15
16
|
"dependencies": {
|
|
16
|
-
"@bridgething/client": "^0.12.0",
|
|
17
17
|
"preact": "^10.29.7",
|
|
18
18
|
"react": "^19.2.0",
|
|
19
19
|
"react-dom": "^19.2.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { settings, type ConfigField, type SettingsContext } from '@bridgething/client/settings';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { useEffect, useState, type FormEvent, type InputEvent } from 'react';
|
|
3
|
+
import { createRoot } from 'react-dom/client';
|
|
4
4
|
import './style.css';
|
|
5
5
|
|
|
6
6
|
function fieldMeta(field: ConfigField): { key: string; label: string } {
|
|
@@ -26,7 +26,7 @@ function Settings() {
|
|
|
26
26
|
})();
|
|
27
27
|
}, []);
|
|
28
28
|
|
|
29
|
-
async function saveConfig(event:
|
|
29
|
+
async function saveConfig(event: FormEvent<HTMLFormElement>) {
|
|
30
30
|
event.preventDefault();
|
|
31
31
|
setStatus('saving...');
|
|
32
32
|
try {
|
|
@@ -53,17 +53,18 @@ function Settings() {
|
|
|
53
53
|
return (
|
|
54
54
|
<main>
|
|
55
55
|
<h1>{ctx?.name ?? '__PROJECT_NAME__'} settings</h1>
|
|
56
|
-
<p
|
|
56
|
+
<p className="hint">{ctx ? `${ctx.webappId} on ${ctx.deviceId}` : 'connecting to the companion host...'}</p>
|
|
57
57
|
|
|
58
58
|
<form onSubmit={saveConfig}>
|
|
59
|
-
{fields.length === 0 && <p
|
|
59
|
+
{fields.length === 0 && <p className="hint">this webapp declares no config fields yet.</p>}
|
|
60
60
|
{fields.map(field => {
|
|
61
61
|
const { key, label } = fieldMeta(field);
|
|
62
62
|
const value = values[key] ?? '';
|
|
63
|
-
const onInput = (e:
|
|
63
|
+
const onInput = (e: InputEvent<HTMLInputElement | HTMLSelectElement>) =>
|
|
64
|
+
setValues({ ...values, [key]: (e.target as HTMLInputElement).value });
|
|
64
65
|
return (
|
|
65
|
-
<div
|
|
66
|
-
<label
|
|
66
|
+
<div className="field" key={key}>
|
|
67
|
+
<label htmlFor={key}>{label}</label>
|
|
67
68
|
{field.type === 'enum' ? (
|
|
68
69
|
<select id={key} value={value} onInput={onInput}>
|
|
69
70
|
{field.data.choices.map(choice => (
|
|
@@ -84,20 +85,20 @@ function Settings() {
|
|
|
84
85
|
);
|
|
85
86
|
})}
|
|
86
87
|
|
|
87
|
-
<div
|
|
88
|
+
<div className="row">
|
|
88
89
|
<button type="submit">Save settings</button>
|
|
89
|
-
<button type="button"
|
|
90
|
+
<button type="button" className="secondary" onClick={saveToDevice}>
|
|
90
91
|
Save to device
|
|
91
92
|
</button>
|
|
92
|
-
<button type="button"
|
|
93
|
+
<button type="button" className="secondary" onClick={() => settings.done()}>
|
|
93
94
|
Done
|
|
94
95
|
</button>
|
|
95
96
|
</div>
|
|
96
97
|
</form>
|
|
97
98
|
|
|
98
|
-
<p
|
|
99
|
+
<p className="status">{status}</p>
|
|
99
100
|
</main>
|
|
100
101
|
);
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
createRoot(document.getElementById('root')!).render(<Settings />);
|
package/template/src/App.tsx
CHANGED
|
@@ -70,7 +70,7 @@ export default function App() {
|
|
|
70
70
|
<div className="mt-6 flex gap-4">
|
|
71
71
|
<button
|
|
72
72
|
className="border border-edge px-6 py-3 font-mono text-row text-near transition active:bg-neutral-soft"
|
|
73
|
-
onClick={() => client.player.skipPrev()}>
|
|
73
|
+
onClick={() => client.player.skipPrev({ allowSeeking: true })}>
|
|
74
74
|
◀◀
|
|
75
75
|
</button>
|
|
76
76
|
<button
|
package/template/tsconfig.json
CHANGED
|
@@ -26,14 +26,14 @@ function sizeGuard(): Plugin {
|
|
|
26
26
|
'',
|
|
27
27
|
'========================================================================',
|
|
28
28
|
` settings.html is ${kib} KiB.`,
|
|
29
|
-
` the
|
|
30
|
-
' trim dependencies or inline assets
|
|
29
|
+
` the device refuses to install a bundle whose settings page is over ${HARD_CAP}.`,
|
|
30
|
+
' trim dependencies or inline assets; the whole install fails, not just this page.',
|
|
31
31
|
'========================================================================',
|
|
32
32
|
'',
|
|
33
33
|
].join('\n'),
|
|
34
34
|
);
|
|
35
35
|
} else if (bytes > WARN_BYTES) {
|
|
36
|
-
console.warn(`settings.html is ${kib} KiB; keep it lean (install
|
|
36
|
+
console.warn(`settings.html is ${kib} KiB; keep it lean (install fails over ${HARD_CAP}).`);
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
39
|
};
|
|
@@ -18,7 +18,7 @@ function sizeGuard(): Plugin {
|
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
20
|
if (bytes > WARN_BYTES) {
|
|
21
|
-
console.warn(`overlay.js is ${(bytes / 1024).toFixed(1)} KiB;
|
|
21
|
+
console.warn(`overlay.js is ${(bytes / 1024).toFixed(1)} KiB; install fails over ${HARD_CAP}.`);
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Building bridgething webapps in this repo
|
|
2
|
+
|
|
3
|
+
This repo is a bun workspace of webapps for the Spotify Car Thing, plus the pipeline that publishes them as a `catalog.v1` source on GitHub Pages. Each `apps/<slug>` is one webapp: a single page running full-screen in the chromium kiosk on the device, reaching the on-device daemon through `@bridgething/client`.
|
|
4
|
+
|
|
5
|
+
## The device
|
|
6
|
+
|
|
7
|
+
The screen is 800x480, landscape, and never resizes. The kiosk shows one webapp, so build in-app views rather than tabs or windows. Add an on-screen keyboard if the app needs text entry.
|
|
8
|
+
|
|
9
|
+
Listen with a `keydown` handler and a `wheel` handler on `window`.
|
|
10
|
+
|
|
11
|
+
| Control | Event |
|
|
12
|
+
| ------------ | ------------------------------------- |
|
|
13
|
+
| Preset 1-4 | `keydown` key `"1"` `"2"` `"3"` `"4"` |
|
|
14
|
+
| Mode | `keydown` key `"m"` |
|
|
15
|
+
| Back | `keydown` key `"Escape"` |
|
|
16
|
+
| Rotary wheel | `wheel` with horizontal `deltaX` |
|
|
17
|
+
| Touch | pointer and touch events |
|
|
18
|
+
|
|
19
|
+
Make horizontal wheel scroll move through the main list. Five fast presses of Mode returns to the launcher.
|
|
20
|
+
|
|
21
|
+
## The client
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { BridgethingClient } from '@bridgething/client';
|
|
25
|
+
import { useMemo } from 'react';
|
|
26
|
+
|
|
27
|
+
import { daemonUrl } from './daemon';
|
|
28
|
+
|
|
29
|
+
const client = useMemo(() => new BridgethingClient({ url: daemonUrl() }), []);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Construct it once and reuse it. It connects and reconnects on its own. Call `daemonUrl()` instead of a literal `ws://` address.
|
|
33
|
+
|
|
34
|
+
Now-playing and library data come from the phone's Spotify, so render a placeholder when no phone is connected. Fetch artwork with `client.asset.get` using the opaque id on the track.
|
|
35
|
+
|
|
36
|
+
Every surface: `player asset config store doc capabilities library audio notifications phone peer geo net hardware bluetooth system time voice lyrics webapp forward`. Each method is an event, a request, or a command.
|
|
37
|
+
|
|
38
|
+
Methods, types, and examples: `.claude/skills/bridgething/reference/sdk.md`. That skill lives once at the repo root and every app symlinks it, so it is one copy for the whole workspace. `bun run skills` refreshes it from the published `create-bridgething`; `bun run check` says when it is behind.
|
|
39
|
+
|
|
40
|
+
## Working on one app
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
bun run dev # the only app, against a connected Car Thing
|
|
44
|
+
bun run dev <slug> # name it when there is more than one
|
|
45
|
+
bun run --cwd apps/<slug> dev # always works
|
|
46
|
+
bun run --cwd apps/<slug> dev:device # show that server on the device's own screen
|
|
47
|
+
bun run --cwd apps/<slug> push # build and install onto the connected device
|
|
48
|
+
bun run --cwd apps/<slug> build # writes dist/
|
|
49
|
+
bun run --cwd apps/<slug> typecheck # checks src, settings and extension together
|
|
50
|
+
bun run --cwd apps/<slug> share # zips dist/ to hand to someone directly
|
|
51
|
+
bun run --cwd apps/<slug> update # brings the device to the latest bridgething release
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
An app with a variant or an extension carries its own `apps/<slug>/CLAUDE.md` with the parts specific to it.
|
|
55
|
+
|
|
56
|
+
Running and driving the app: `.claude/skills/bridgething/reference/develop.md`. Push, zip, update: `.claude/skills/bridgething/reference/ship.md`. The desktop-side Deno extension and its permission model, for an app with an `extension/` directory: `.claude/skills/bridgething/reference/extension.md`.
|
|
57
|
+
|
|
58
|
+
## The three files that describe an app
|
|
59
|
+
|
|
60
|
+
**`apps/<slug>/public/manifest.json`** is the app's identity as far as the device and the catalog are concerned.
|
|
61
|
+
|
|
62
|
+
- `id` identifies this webapp on the device. It is a uuidv7 generated once at scaffold time. **Never change it**: the device keys upgrade-in-place and the app's key-value namespace on it, so a new uuid orphans everyone's installed copy and its data.
|
|
63
|
+
- `version` is what the store publishes against. Move it with `bun run bump`, never by hand.
|
|
64
|
+
- `description` is the store tagline and must not be empty.
|
|
65
|
+
- `config` declares the settings the companion app edits and `client.config` reads.
|
|
66
|
+
- `permissions` grants `geo` and `net.proxy`.
|
|
67
|
+
- `art.heroPx` and `art.thumbPx` are the sizes artwork arrives at.
|
|
68
|
+
- `settings` names the page built from `settings/`, capped at 1 MiB. It talks to the companion app through `@bridgething/client/settings`.
|
|
69
|
+
- `extension` declares the desktop-side Deno process and the host permissions it gets.
|
|
70
|
+
|
|
71
|
+
**`apps/<slug>/catalog.json`** is the store listing and nothing else: `author`, `homepage`, `source`, an `icon` override, `screenshots`, and `min_libbridgething_version` (the oldest daemon the app works against). Never restate anything the manifest already says. An app that ships an extension must carry its `github.com` repo url in `source`, because the store shows that link next to the host permissions it is asking for.
|
|
72
|
+
|
|
73
|
+
**`apps/<slug>/CHANGELOG.md`** carries a `## <version>` section per release, which becomes that version's changelog in the catalog.
|
|
74
|
+
|
|
75
|
+
## Adding and shipping
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
bun run new <slug> [--extension | --launcher | --overlay]
|
|
79
|
+
bun run shot <slug> [--replace | --name <label>]
|
|
80
|
+
bun run bump <slug> <major | minor | patch | x.y.z> [-m "note"]
|
|
81
|
+
bun run check
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`new` scaffolds into `apps/<slug>`. Never add an app by hand: the scaffold generates the uuid, writes the store listing, wires the dev server to a connected device, and lands the tsconfig and scripts CI expects.
|
|
85
|
+
|
|
86
|
+
`shot` captures the kiosk over CDP into `apps/<slug>/screenshots/`. Chromium's debugging port is 9223 and is bound to the device's loopback, so it goes through an ssh tunnel; the command opens and closes one itself. Up to six, filename order, first is the store card.
|
|
87
|
+
|
|
88
|
+
`bump` writes `public/manifest.json` and `package.json` together and opens the changelog section. Editing one without the other fails the build.
|
|
89
|
+
|
|
90
|
+
`check` is the whole gate and is exactly what CI runs: typecheck, build, bundle, generate the catalog, validate it against `catalog.v1` and the cross-reference invariants. Run it before claiming anything works.
|
|
91
|
+
|
|
92
|
+
Pushing to main publishes. `bun run publish --dry-run` assembles the exact bytes that would be pushed, into `site/`, without pushing.
|
|
93
|
+
|
|
94
|
+
## Publishing rules that are not negotiable
|
|
95
|
+
|
|
96
|
+
- **A published version is immutable.** `published.json` on the `gh-pages` branch records the sha256 clients verify against. Changing an app means a new version, always. `check` fails a pull request that changed an app without bumping it.
|
|
97
|
+
- **Bundles are reproducible.** Timestamps are flattened before zipping so the same source gives the same digest. Do not add anything nondeterministic to a build.
|
|
98
|
+
- **`site/` is generated and gitignored.** Nothing about a release belongs on main.
|
|
99
|
+
- **CORS is a requirement.** A source must serve its catalog and its downloads with `Access-Control-Allow-Origin: *` or browser clients cannot read it. GitHub Pages does; most self-hosting does not.
|
|
100
|
+
- **`base_url` in `source.json` is where this catalog is served.** If you forked this repo, point it at your own pages site and give every app a fresh uuid, or you publish app ids that belong to someone else. `check` refuses when it disagrees with the origin remote.
|
|
101
|
+
|
|
102
|
+
## Comments
|
|
103
|
+
|
|
104
|
+
Terse, lowercase, self-contained, non-obvious WHY only, 120 columns. No historical cross-references, no pointers at other files, no emdashes. Most code here needs none.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# **SOURCE_NAME**
|
|
2
|
+
|
|
3
|
+
**SOURCE_DESCRIPTION**
|
|
4
|
+
|
|
5
|
+
Webapps for the Spotify Car Thing running [bridgething](https://bridgething.com).
|
|
6
|
+
|
|
7
|
+
## First run
|
|
8
|
+
|
|
9
|
+
1. Push this repo to `https://github.com/__REPO_OWNER__/__REPO_NAME__`.
|
|
10
|
+
2. In **Settings > Pages**, set the source to **Deploy from a branch**, branch `gh-pages`, folder `/ (root)`.
|
|
11
|
+
|
|
12
|
+
The catalog is published to `__BASE_URL__/catalog.v1.json`, which can be submitted to <bridgething.com/apps>
|
|
13
|
+
|
|
14
|
+
## Develop
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
bun run dev # develop the app against a connected bridgething instance
|
|
18
|
+
bun run dev:device # show the dev server on the car thing screen
|
|
19
|
+
bun run push # build and install to the device
|
|
20
|
+
bun run check # ensure the catalog is valid
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
With more than one app in `apps/` your commands must specify which one: `bun run dev __APP_SLUG__`.
|
|
24
|
+
|
|
25
|
+
Screenshot for the store listing:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
bun run shot __APP_SLUG__ # grabs what is on the screen
|
|
29
|
+
bun run shot __APP_SLUG__ --replace # overwrite
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Add another app
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
bun run new weather # a webapp
|
|
36
|
+
bun run new dashboard --extension # a webapp plus a desktop-side Deno process
|
|
37
|
+
bun run new home --launcher # a replacement home screen
|
|
38
|
+
bun run new hud --overlay # a system overlay drawn over every webapp
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Ship
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
bun run bump __APP_SLUG__ patch -m "Fix the wind direction arrow"
|
|
45
|
+
git commit -am "__APP_SLUG__: fix the wind direction arrow" && git push
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Pushing to main builds the apps and regenerates the catalog.
|
|
49
|
+
|
|
50
|
+
## Agent skill
|
|
51
|
+
|
|
52
|
+
`.claude/skills/bridgething/` holds the `/bridgething` skill.
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
bun run skills # refresh it from the published create-bridgething
|
|
56
|
+
bun run skills --check # check whether it is behind
|
|
57
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.4.0
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
check:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
with:
|
|
17
|
+
fetch-depth: 0
|
|
18
|
+
|
|
19
|
+
- uses: oven-sh/setup-bun@v2
|
|
20
|
+
with:
|
|
21
|
+
bun-version-file: .bun-version
|
|
22
|
+
|
|
23
|
+
- run: bun install --frozen-lockfile
|
|
24
|
+
|
|
25
|
+
- name: build, bundle and validate the catalog
|
|
26
|
+
env:
|
|
27
|
+
CHECK_BASE_REF: ${{ github.event_name == 'pull_request' && format('origin/{0}', github.base_ref) || '' }}
|
|
28
|
+
run: bun run check
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: publish
|
|
10
|
+
cancel-in-progress: false
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
publish:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- uses: oven-sh/setup-bun@v2
|
|
22
|
+
with:
|
|
23
|
+
bun-version-file: .bun-version
|
|
24
|
+
|
|
25
|
+
- run: bun install --frozen-lockfile
|
|
26
|
+
|
|
27
|
+
- name: identify the publisher
|
|
28
|
+
run: |
|
|
29
|
+
git config user.name "github-actions[bot]"
|
|
30
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
31
|
+
|
|
32
|
+
- name: build every unpublished version and push the catalog
|
|
33
|
+
run: bun run publish
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** @type {import("prettier").Config} */
|
|
2
|
+
export default {
|
|
3
|
+
bracketSpacing: true,
|
|
4
|
+
bracketSameLine: true,
|
|
5
|
+
singleQuote: true,
|
|
6
|
+
trailingComma: 'all',
|
|
7
|
+
arrowParens: 'avoid',
|
|
8
|
+
semi: true,
|
|
9
|
+
overrides: [{ files: ['*.ts', '*.js', '*.tsx', '*.jsx', '*.cjs', '*.mjs'], options: { printWidth: 120 } }],
|
|
10
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__REPO_NAME__",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"workspaces": [
|
|
6
|
+
"apps/*"
|
|
7
|
+
],
|
|
8
|
+
"scripts": {
|
|
9
|
+
"new": "bunx create-bridgething",
|
|
10
|
+
"dev": "bridgething-source run dev",
|
|
11
|
+
"dev:device": "bridgething-source run dev:device",
|
|
12
|
+
"build": "bridgething-source run build",
|
|
13
|
+
"push": "bridgething-source run push",
|
|
14
|
+
"share": "bridgething-source run share",
|
|
15
|
+
"check": "bridgething-source check",
|
|
16
|
+
"bump": "bridgething-source bump",
|
|
17
|
+
"shot": "bridgething-source shot",
|
|
18
|
+
"catalog": "bridgething-source catalog",
|
|
19
|
+
"publish": "bridgething-source publish",
|
|
20
|
+
"skills": "bridgething-source skills",
|
|
21
|
+
"format": "prettier --write \"*.json\" \"*.js\""
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"prettier": "^3.9.6",
|
|
25
|
+
"typescript": "^6.0.3"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/template/CLAUDE.md
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# Building this bridgething webapp
|
|
2
|
-
|
|
3
|
-
A bridgething webapp is one page running full-screen in the chromium kiosk on a
|
|
4
|
-
Spotify Car Thing. It reaches the on-device daemon through `@bridgething/client`.
|
|
5
|
-
|
|
6
|
-
## The device
|
|
7
|
-
|
|
8
|
-
The screen is 800x480, landscape, and never resizes. The kiosk shows one webapp,
|
|
9
|
-
so build in-app views rather than tabs or windows. Add an on-screen keyboard if
|
|
10
|
-
the app needs text entry.
|
|
11
|
-
|
|
12
|
-
Listen with a `keydown` handler and a `wheel` handler on `window`.
|
|
13
|
-
|
|
14
|
-
| Control | Event |
|
|
15
|
-
| ------------ | ------------------------------------- |
|
|
16
|
-
| Preset 1-4 | `keydown` key `"1"` `"2"` `"3"` `"4"` |
|
|
17
|
-
| Mode | `keydown` key `"m"` |
|
|
18
|
-
| Back | `keydown` key `"Escape"` |
|
|
19
|
-
| Rotary wheel | `wheel` with horizontal `deltaX` |
|
|
20
|
-
| Touch | pointer and touch events |
|
|
21
|
-
|
|
22
|
-
Make horizontal wheel scroll move through the main list. Five fast presses of
|
|
23
|
-
Mode returns to the launcher.
|
|
24
|
-
|
|
25
|
-
## The client
|
|
26
|
-
|
|
27
|
-
```ts
|
|
28
|
-
import { BridgethingClient } from '@bridgething/client';
|
|
29
|
-
import { useMemo } from 'react';
|
|
30
|
-
|
|
31
|
-
import { daemonUrl } from './daemon';
|
|
32
|
-
|
|
33
|
-
const client = useMemo(() => new BridgethingClient({ url: daemonUrl() }), []);
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Construct it once and reuse it. It connects and reconnects on its own. Call
|
|
37
|
-
`daemonUrl()` instead of a literal `ws://` address.
|
|
38
|
-
|
|
39
|
-
Now-playing and library data come from the phone's Spotify, so render a
|
|
40
|
-
placeholder when no phone is connected. Fetch artwork with `client.asset.get`
|
|
41
|
-
using the opaque id on the track.
|
|
42
|
-
|
|
43
|
-
Every surface: `player asset config store doc capabilities library audio
|
|
44
|
-
notifications phone peer geo net hardware bluetooth system time voice lyrics
|
|
45
|
-
webapp forward`. Each method is an event, a request, or a command.
|
|
46
|
-
|
|
47
|
-
Methods, types, and examples: `.claude/skills/bridgething/reference/sdk.md`.
|
|
48
|
-
|
|
49
|
-
## `public/manifest.json`
|
|
50
|
-
|
|
51
|
-
- `id` identifies this webapp on the device. Keep it.
|
|
52
|
-
- `version` tells builds apart. Raise it before sharing an update.
|
|
53
|
-
- `config` declares the settings the companion app edits and `client.config`
|
|
54
|
-
reads.
|
|
55
|
-
- `permissions` grants `geo` and `net.proxy`.
|
|
56
|
-
- `art.heroPx` and `art.thumbPx` are the sizes artwork arrives at.
|
|
57
|
-
- `settings` names the page built from `settings/`, capped at 1 MiB. It talks to
|
|
58
|
-
the companion app through `@bridgething/client/settings`.
|
|
59
|
-
|
|
60
|
-
## Workflow
|
|
61
|
-
|
|
62
|
-
- `bun run dev` serves at `http://localhost:5173/` against the connected Car
|
|
63
|
-
Thing.
|
|
64
|
-
- `bun run dev:device` shows that server on the device's own screen.
|
|
65
|
-
- `bun run build` writes `dist/`.
|
|
66
|
-
- `bun run push` builds and installs onto the connected device.
|
|
67
|
-
- `bun run share` zips `dist/`.
|
|
68
|
-
- `bun run update` brings the device to the latest bridgething release.
|
|
69
|
-
|
|
70
|
-
Running and driving the app: `.claude/skills/bridgething/reference/develop.md`.
|
|
71
|
-
Push, zip, update: `.claude/skills/bridgething/reference/ship.md`.
|
package/template/README.md
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# **PROJECT_NAME**
|
|
2
|
-
|
|
3
|
-
A bridgething webapp scaffolded with `create-bridgething`. It runs full-screen in
|
|
4
|
-
the chromium kiosk on a Spotify Car Thing and talks to the on-device daemon
|
|
5
|
-
through [`@bridgething/client`](https://github.com/JoeyEamigh/bridgething).
|
|
6
|
-
|
|
7
|
-
Stack: React 19, Vite, Tailwind v4, TypeScript strict.
|
|
8
|
-
|
|
9
|
-
Open this folder with your coding agent. It reads `CLAUDE.md` or `AGENTS.md` and
|
|
10
|
-
the `/bridgething` skill.
|
|
11
|
-
|
|
12
|
-
## Develop
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
bun install
|
|
16
|
-
bun run dev
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Vite serves at http://localhost:5173/ with hot reload, against the daemon on the
|
|
20
|
-
Car Thing plugged in over USB. Set `SUPERBIRD_HOST` to target another device, or
|
|
21
|
-
`BRIDGETHING_DAEMON_URL` to reach a daemon elsewhere.
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
bun run dev:device
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
The same server, shown on the Car Thing's own screen, hot reload included.
|
|
28
|
-
Ctrl-C hands the screen back to the installed build.
|
|
29
|
-
|
|
30
|
-
When the project ships an extension (`extension/main.ts`), both commands bundle
|
|
31
|
-
it on every save and run it under Deno with the permissions the manifest
|
|
32
|
-
declares.
|
|
33
|
-
|
|
34
|
-
## Push, share, update
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
bun run push # build, copy to the Car Thing, switch the kiosk to it
|
|
38
|
-
bun run build
|
|
39
|
-
bun run share # write <name>-<version>.zip from dist/
|
|
40
|
-
bun run update # bring the device to the latest bridgething release
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
`push` targets `bridgething.local` over USB; pass a host or IP address for
|
|
44
|
-
another device. Anyone with a bridgething Car Thing installs the zip from the
|
|
45
|
-
companion app. For one device out of several,
|
|
46
|
-
`bun run update -- --host ws://bridgething-<serial>.local:8892/`.
|
|
47
|
-
|
|
48
|
-
## Layout
|
|
49
|
-
|
|
50
|
-
- `src/App.tsx` subscribes to `client.player.onSnapshot`, fetches artwork through
|
|
51
|
-
`client.asset.get`, and draws transport controls.
|
|
52
|
-
- `src/daemon.ts` returns the daemon URL.
|
|
53
|
-
- `public/manifest.json` carries the webapp id, version, config schema, and
|
|
54
|
-
permissions.
|
|
55
|
-
- `settings/` is the settings page the companion app renders, built to
|
|
56
|
-
`dist/settings.html`.
|
|
57
|
-
- `index.html` and `vite.config.ts` fix the 800x480 viewport and the build target.
|