pi-central 0.3.0 → 0.4.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/README.md +58 -17
- package/bin/pi-central.js +143 -10
- package/bin/pi-central.js.map +4 -4
- package/dist/ui/apple-touch-icon.png +0 -0
- package/dist/ui/assets/{grammar-map-D-x_DcfQ.js → grammar-map-BNDc6jQR.js} +1 -1
- package/dist/ui/assets/index-CPKDQr93.css +1 -0
- package/dist/ui/assets/index-DO0lG4JX.js +43 -0
- package/dist/ui/assets/logo-icon-BCSgj5RT.svg +23 -0
- package/dist/ui/assets/logo-icon-CYTM3sb_.png +0 -0
- package/dist/ui/icon-192.png +0 -0
- package/dist/ui/icon-512.png +0 -0
- package/dist/ui/index.html +33 -3
- package/dist/ui/manifest.webmanifest +30 -0
- package/dist/ui/sw.js +93 -0
- package/package.json +1 -1
- package/dist/ui/assets/index-CDs5_ZFh.js +0 -40
- package/dist/ui/assets/index-Deb-EFhq.css +0 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<svg
|
|
3
|
+
width="27.417528mm"
|
|
4
|
+
height="27.417528mm"
|
|
5
|
+
viewBox="0 0 27.417529 27.417529"
|
|
6
|
+
version="1.1"
|
|
7
|
+
id="svg1"
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
|
10
|
+
<g
|
|
11
|
+
id="layer1"
|
|
12
|
+
transform="translate(-25.653968,-66.727691)">
|
|
13
|
+
<path
|
|
14
|
+
id="path52"
|
|
15
|
+
style="fill:#3fa383;stroke-width:3.02717;stroke-linejoin:round;paint-order:stroke fill markers"
|
|
16
|
+
d="M 53.071496,80.436455 A 13.708764,13.708764 0 0 1 39.362732,94.145219 13.708764,13.708764 0 0 1 25.653968,80.436455 13.708764,13.708764 0 0 1 39.362732,66.727691 13.708764,13.708764 0 0 1 53.071496,80.436455 Z" />
|
|
17
|
+
<path
|
|
18
|
+
style="font-weight:600;font-size:30.375px;line-height:2;font-family:Roboto;-inkscape-font-specification:'Roboto, Semi-Bold';letter-spacing:7.50712px;fill:#ffffff;stroke-width:4.48942;stroke-linejoin:round;paint-order:stroke fill markers"
|
|
19
|
+
d="m 48.046608,72.559311 v 2.951477 H 30.530556 v -2.951477 z m -10.975342,0 V 88.60704 H 33.140907 V 72.559311 Z m 4.523621,0 h 3.930359 l -0.01483,11.286804 q 0,0.726746 0.19281,1.112366 0.207642,0.370788 0.57843,0.519104 0.38562,0.133484 0.934388,0.133484 0.38562,0 0.711914,-0.04449 0.326294,-0.04449 0.548767,-0.08899 l 0.01483,2.936645 q -0.504272,0.148316 -1.112365,0.252137 -0.608094,0.118652 -1.379334,0.118652 -1.320007,0 -2.313721,-0.459778 -0.993713,-0.459778 -1.54248,-1.468323 -0.548767,-1.023376 -0.548767,-2.684509 z"
|
|
20
|
+
id="text52"
|
|
21
|
+
aria-label="π" />
|
|
22
|
+
</g>
|
|
23
|
+
</svg>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/ui/index.html
CHANGED
|
@@ -5,11 +5,41 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<meta
|
|
7
7
|
http-equiv="Content-Security-Policy"
|
|
8
|
-
content="default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'"
|
|
8
|
+
content="default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'"
|
|
9
9
|
/>
|
|
10
10
|
<title>Pi Central</title>
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
<!--
|
|
12
|
+
An SVG icon first, because it is one file that stays sharp at every size, with a PNG fallback for
|
|
13
|
+
anything that cannot use it. `sizes="any"` tells the browser the SVG is scalable rather than
|
|
14
|
+
claiming a pixel size it does not have.
|
|
15
|
+
|
|
16
|
+
Both live in `src/assets/` so Vite fingerprints and emits them with the rest of the build — which
|
|
17
|
+
also means the orchestrator serves them with the right content type (`static-ui.ts` has `.svg` and
|
|
18
|
+
`.png`), rather than the app shipping a favicon the server would call `application/octet-stream`.
|
|
19
|
+
-->
|
|
20
|
+
<link rel="icon" type="image/svg+xml" href="/assets/logo-icon-BCSgj5RT.svg" sizes="any" />
|
|
21
|
+
<link rel="icon" type="image/png" href="/assets/logo-icon-CYTM3sb_.png" />
|
|
22
|
+
<!--
|
|
23
|
+
Safari's **Add to Dock** does not read the manifest's icons — it reads this link. So it has to be
|
|
24
|
+
here, at 180×180, at a STABLE path (issue #124).
|
|
25
|
+
|
|
26
|
+
Three faults in the previous version, and the symptom was exactly what they predict:
|
|
27
|
+
- it pointed at the 104×104 PNG, and Safari wants 180;
|
|
28
|
+
- it pointed into `src/assets/`, so the path was FINGERPRINTED — an icon URL is cached hard by
|
|
29
|
+
macOS and iOS, and a name that changes every build defeats that cache in the worst direction;
|
|
30
|
+
- and it was the only icon on a stable path that Safari could find, so when it rejected this one
|
|
31
|
+
there was nothing to fall back to but a screenshot of the page.
|
|
32
|
+
-->
|
|
33
|
+
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
34
|
+
<meta name="theme-color" content="#3fa383" />
|
|
35
|
+
<!--
|
|
36
|
+
Installable to the dock as a standalone window (issue #119). Deliberately NOT an offline app: every
|
|
37
|
+
screen is fed by the socket, so an installed Pi Central with the orchestrator down shows
|
|
38
|
+
`disconnected`. The manifest says `display: standalone`, not "works offline".
|
|
39
|
+
-->
|
|
40
|
+
<link rel="manifest" href="/manifest.webmanifest" />
|
|
41
|
+
<script type="module" crossorigin src="/assets/index-DO0lG4JX.js"></script>
|
|
42
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CPKDQr93.css">
|
|
13
43
|
</head>
|
|
14
44
|
<body>
|
|
15
45
|
<div id="root"></div>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Pi Central",
|
|
3
|
+
"short_name": "Pi Central",
|
|
4
|
+
"description": "A local dashboard for running, watching, and switching between multiple sandboxed Pi coding-agent sessions.",
|
|
5
|
+
"start_url": "/",
|
|
6
|
+
"scope": "/",
|
|
7
|
+
"display": "standalone",
|
|
8
|
+
"background_color": "#0e1116",
|
|
9
|
+
"theme_color": "#3fa383",
|
|
10
|
+
"icons": [
|
|
11
|
+
{
|
|
12
|
+
"src": "/apple-touch-icon.png",
|
|
13
|
+
"sizes": "180x180",
|
|
14
|
+
"type": "image/png",
|
|
15
|
+
"purpose": "any"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"src": "/icon-192.png",
|
|
19
|
+
"sizes": "192x192",
|
|
20
|
+
"type": "image/png",
|
|
21
|
+
"purpose": "any"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"src": "/icon-512.png",
|
|
25
|
+
"sizes": "512x512",
|
|
26
|
+
"type": "image/png",
|
|
27
|
+
"purpose": "any"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
package/dist/ui/sw.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The service worker (issue #119).
|
|
3
|
+
*
|
|
4
|
+
* ## What this is for, precisely
|
|
5
|
+
*
|
|
6
|
+
* A standalone window and a dock icon. **Not offline** — every screen is fed by the socket, so an
|
|
7
|
+
* installed Pi Central with the orchestrator down shows `disconnected` and nothing else. A worker that
|
|
8
|
+
* "worked offline" would install, open, and be useless while implying otherwise, so it deliberately does
|
|
9
|
+
* not try.
|
|
10
|
+
*
|
|
11
|
+
* ## The one rule that matters: `index.html` is NEVER cached
|
|
12
|
+
*
|
|
13
|
+
* `index.html` names the current fingerprint set, which is exactly what `static-ui.ts` says in its
|
|
14
|
+
* `Cache-Control`:
|
|
15
|
+
*
|
|
16
|
+
* // `index.html` names the current fingerprint set, so caching it is exactly the mistake that
|
|
17
|
+
* // serves a stale app after a rebuild.
|
|
18
|
+
*
|
|
19
|
+
* A worker that cached it would make a stale build **permanent and invisible** — no reload button, no URL
|
|
20
|
+
* to compare, and a worker the browser updates on its own schedule. So this file applies the SERVER's
|
|
21
|
+
* rule: hashed assets are immutable and cached forever, and the document is fetched from the network every
|
|
22
|
+
* time (issue #120 guards this).
|
|
23
|
+
*
|
|
24
|
+
* ## Why there is no precache list
|
|
25
|
+
*
|
|
26
|
+
* The build emits 266 chunks and 8.8 MB, dominated by Shiki grammars (`elisp` 772 kB, `cpp` 492 kB) that a
|
|
27
|
+
* session may never open. Precaching them would make the first visit pay for grammars it does not use. So
|
|
28
|
+
* everything is runtime-cached on FIRST REQUEST: the entry chunk and CSS on load, a grammar the first time
|
|
29
|
+
* that language appears. Nothing is fetched eagerly.
|
|
30
|
+
*
|
|
31
|
+
* ## `skipWaiting` is deliberately NOT used
|
|
32
|
+
*
|
|
33
|
+
* "Activate immediately" and "never interrupt a running session" pull in opposite directions, and this app
|
|
34
|
+
* holds live sessions. A worker that took over mid-stream would reload the UI under the reader. The new
|
|
35
|
+
* worker waits, and takes over on the next launch — which is the right trade for a tool that is open for
|
|
36
|
+
* hours at a time.
|
|
37
|
+
*/
|
|
38
|
+
const CACHE = "pi-central-v1";
|
|
39
|
+
|
|
40
|
+
// A request for the app document, or for a directory that resolves to one.
|
|
41
|
+
function isDocument(request, url) {
|
|
42
|
+
return request.mode === "navigate" || url.pathname === "/" || url.pathname.endsWith("/index.html");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
self.addEventListener("install", (event) => {
|
|
46
|
+
// Nothing to precache — see the note above. `skipWaiting` is absent on purpose.
|
|
47
|
+
event.waitUntil(Promise.resolve());
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
self.addEventListener("activate", (event) => {
|
|
51
|
+
// Drop caches from previous versions of this worker, so a schema change cannot leave stale bytes around
|
|
52
|
+
// forever. Scoped to this prefix so another app on the same origin is untouched.
|
|
53
|
+
event.waitUntil(
|
|
54
|
+
caches.keys().then((names) =>
|
|
55
|
+
Promise.all(names.filter((name) => name.startsWith("pi-central-") && name !== CACHE).map((name) => caches.delete(name))),
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
self.addEventListener("fetch", (event) => {
|
|
61
|
+
const request = event.request;
|
|
62
|
+
if (request.method !== "GET") return;
|
|
63
|
+
|
|
64
|
+
const url = new URL(request.url);
|
|
65
|
+
// Same-origin only. The app has no business proxying anything else, and doing so would be a hole.
|
|
66
|
+
if (url.origin !== self.location.origin) return;
|
|
67
|
+
|
|
68
|
+
// The socket is not a fetch, but a dev-server proxy or an `http` fallback could arrive here. Leave it.
|
|
69
|
+
if (url.pathname.startsWith("/ws")) return;
|
|
70
|
+
|
|
71
|
+
if (isDocument(request, url)) {
|
|
72
|
+
// NETWORK, always. No `cache.put`, no fallback to a cached document — a cached `index.html` is how a
|
|
73
|
+
// rebuild stops being visible. If the orchestrator is down the request fails, which is the honest
|
|
74
|
+
// outcome for an app that cannot work without it.
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Hashed assets: cache-first, because their name cannot change under new content.
|
|
79
|
+
event.respondWith(
|
|
80
|
+
caches.match(request).then((cached) => {
|
|
81
|
+
if (cached !== undefined) return cached;
|
|
82
|
+
return fetch(request).then((response) => {
|
|
83
|
+
// Only a real, complete same-origin response is worth keeping. `opaque` responses have no usable
|
|
84
|
+
// status, and a 404 or a 500 must not become permanent.
|
|
85
|
+
if (response.ok && response.type === "basic") {
|
|
86
|
+
const copy = response.clone();
|
|
87
|
+
caches.open(CACHE).then((cache) => cache.put(request, copy));
|
|
88
|
+
}
|
|
89
|
+
return response;
|
|
90
|
+
});
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
});
|