@timqi/pier 0.1.11 → 0.1.12
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/boards/pier.css +37 -22
- package/dist/channels/config.js +24 -2
- package/dist/channels/routes.js +13 -1
- package/dist/main.js +1 -1
- package/dist/web/public/assets/{activity-dxSOEHmf.js → activity-CJtsniqS.js} +2 -2
- package/dist/web/public/assets/activity-CJtsniqS.js.br +0 -0
- package/dist/web/public/assets/activity-CJtsniqS.js.gz +0 -0
- package/dist/web/public/assets/boards-DyQtNqgv.js +1 -0
- package/dist/web/public/assets/boards-DyQtNqgv.js.br +0 -0
- package/dist/web/public/assets/boards-DyQtNqgv.js.gz +0 -0
- package/dist/web/public/assets/explorer-BQYNIR0V.js +4 -0
- package/dist/web/public/assets/explorer-BQYNIR0V.js.br +0 -0
- package/dist/web/public/assets/explorer-BQYNIR0V.js.gz +0 -0
- package/dist/web/public/assets/index-CLNTSmYN.js +85 -0
- package/dist/web/public/assets/index-CLNTSmYN.js.br +0 -0
- package/dist/web/public/assets/index-CLNTSmYN.js.gz +0 -0
- package/dist/web/public/assets/index-DiHj0w1i.css +2 -0
- package/dist/web/public/assets/index-DiHj0w1i.css.br +0 -0
- package/dist/web/public/assets/index-DiHj0w1i.css.gz +0 -0
- package/dist/web/public/assets/{runs-DtvDXSfo.js → runs-tYH577mL.js} +1 -1
- package/dist/web/public/assets/runs-tYH577mL.js.br +0 -0
- package/dist/web/public/assets/runs-tYH577mL.js.gz +0 -0
- package/dist/web/public/assets/settings-B42zHcpu.js +5 -0
- package/dist/web/public/assets/settings-B42zHcpu.js.br +0 -0
- package/dist/web/public/assets/settings-B42zHcpu.js.gz +0 -0
- package/dist/web/public/assets/{task-runs-DcORJXHU.js → task-runs-COIFbwwz.js} +2 -2
- package/dist/web/public/assets/task-runs-COIFbwwz.js.br +0 -0
- package/dist/web/public/assets/task-runs-COIFbwwz.js.gz +0 -0
- package/dist/web/public/assets/tasks-D1dGhkTZ.js +4 -0
- package/dist/web/public/assets/tasks-D1dGhkTZ.js.br +0 -0
- package/dist/web/public/assets/tasks-D1dGhkTZ.js.gz +0 -0
- package/dist/web/public/index.html +2 -2
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/package.json +1 -1
- package/skills/pier-boards/SKILL.md +89 -124
- package/dist/web/public/assets/activity-dxSOEHmf.js.br +0 -0
- package/dist/web/public/assets/activity-dxSOEHmf.js.gz +0 -0
- package/dist/web/public/assets/boards-BRwn5UZi.js +0 -1
- package/dist/web/public/assets/boards-BRwn5UZi.js.br +0 -0
- package/dist/web/public/assets/boards-BRwn5UZi.js.gz +0 -0
- package/dist/web/public/assets/explorer-CCJQ22Dp.js +0 -4
- package/dist/web/public/assets/explorer-CCJQ22Dp.js.br +0 -0
- package/dist/web/public/assets/explorer-CCJQ22Dp.js.gz +0 -0
- package/dist/web/public/assets/index-CzV2cZEH.js +0 -85
- package/dist/web/public/assets/index-CzV2cZEH.js.br +0 -0
- package/dist/web/public/assets/index-CzV2cZEH.js.gz +0 -0
- package/dist/web/public/assets/index-sQWFDUPu.css +0 -2
- package/dist/web/public/assets/index-sQWFDUPu.css.br +0 -0
- package/dist/web/public/assets/index-sQWFDUPu.css.gz +0 -0
- package/dist/web/public/assets/runs-DtvDXSfo.js.br +0 -0
- package/dist/web/public/assets/runs-DtvDXSfo.js.gz +0 -0
- package/dist/web/public/assets/settings-CJbHCEZo.js +0 -5
- package/dist/web/public/assets/settings-CJbHCEZo.js.br +0 -0
- package/dist/web/public/assets/settings-CJbHCEZo.js.gz +0 -0
- package/dist/web/public/assets/task-runs-DcORJXHU.js.br +0 -0
- package/dist/web/public/assets/task-runs-DcORJXHU.js.gz +0 -0
- package/dist/web/public/assets/tasks-8l1M8EJU.js +0 -4
- package/dist/web/public/assets/tasks-8l1M8EJU.js.br +0 -0
- package/dist/web/public/assets/tasks-8l1M8EJU.js.gz +0 -0
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,150 +1,115 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pier-boards
|
|
3
|
-
description: Publish a Board —
|
|
3
|
+
description: Publish a Board — static HTML at a stable Pier URL. Read before creating or editing a page-shaped deliverable (report, digest, dashboard).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Boards
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
Use the exact boards folder and instance address in "This Pier instance" in
|
|
9
|
+
`<pier>/AGENTS.md`, not an assumed `~/.pier`. Only `<slug>/site/` is served;
|
|
10
|
+
boards persist across sessions. Update existing boards in place, preserving URLs.
|
|
11
|
+
Before editing, read the board's README if present; change sources and rebuild.
|
|
11
12
|
|
|
12
|
-
##
|
|
13
|
+
## Files and visibility
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
<boards folder>/weekly-digest/
|
|
16
|
-
board.json
|
|
17
|
-
site/index.html
|
|
18
|
-
```
|
|
15
|
+
Create `<boards folder>/<slug>/board.json` and `site/index.html`:
|
|
19
16
|
|
|
20
17
|
```json
|
|
21
|
-
{
|
|
22
|
-
"title": "Weekly digest — infra",
|
|
23
|
-
"description": "What changed in infra this week and what needs a decision.",
|
|
24
|
-
"sessions": ["<your session id>"],
|
|
25
|
-
"public": false
|
|
26
|
-
}
|
|
18
|
+
{"title":"Weekly digest","description":"What changed and needs a decision","sessions":["<session id>"],"public":false}
|
|
27
19
|
```
|
|
28
20
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
- `sessions`: append your id; preserve others as provenance.
|
|
32
|
-
|
|
33
|
-
## Publish, then hand over the link
|
|
34
|
-
|
|
35
|
-
`"public": true` serves the board **with no password**. Set it only if the user
|
|
36
|
-
asked for a public or shareable board *in this request*; otherwise leave it
|
|
37
|
-
`false` and say the board is private. Never publish personal data or anything
|
|
38
|
-
the user has not seen.
|
|
21
|
+
Slug: `[a-z0-9][a-z0-9-]{0,63}`, meaningful without random suffixes. Description
|
|
22
|
+
is Console list text; append your session id, preserving existing provenance.
|
|
39
23
|
|
|
40
|
-
|
|
41
|
-
|
|
24
|
+
`public: true` removes the password. Set it only when this request asks for a
|
|
25
|
+
public/shareable board; otherwise keep it private. Never publish personal data
|
|
26
|
+
or content the user has not seen. Preserve the manifest's token; if missing,
|
|
27
|
+
generate eight hex characters with `openssl rand -hex 4`, never invent/reuse one.
|
|
42
28
|
|
|
43
|
-
|
|
29
|
+
Return one bare URL using the configured instance address:
|
|
44
30
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
| Private (default) | `/boards/<slug>/` — password required; Console → Boards can publish |
|
|
48
|
-
| Public | `/p/<slug>-<token>/` — copy the token verbatim; 404 unless `public: true` |
|
|
31
|
+
- Private: `/boards/<slug>/` (password required).
|
|
32
|
+
- Public: `/p/<slug>-<token>/` (copy the token verbatim; requires `public: true`).
|
|
49
33
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
34
|
+
Never return both URLs, link labels or filesystem paths. Without a configured
|
|
35
|
+
address, return the path and point to Console → Settings; never guess a host.
|
|
36
|
+
For publish-only requests, set visibility/token and return the URL; skip layout
|
|
37
|
+
checks and narration. Console → Boards can also publish.
|
|
54
38
|
|
|
55
|
-
##
|
|
39
|
+
## Page
|
|
56
40
|
|
|
57
|
-
|
|
58
|
-
|
|
41
|
+
Semantic HTML, the user's language, no framework. Include doctype, `lang`, UTF-8,
|
|
42
|
+
viewport metadata, a descriptive title and:
|
|
59
43
|
|
|
60
44
|
```html
|
|
61
|
-
<!doctype html>
|
|
62
|
-
<html lang="en">
|
|
63
|
-
<head>
|
|
64
|
-
<meta charset="utf-8">
|
|
65
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
66
|
-
<title>Weekly digest — infra</title>
|
|
67
45
|
<link rel="stylesheet" href="/p/_assets/pier.css">
|
|
68
|
-
</head>
|
|
69
|
-
<body>
|
|
70
|
-
<h1>Weekly digest — infra</h1>
|
|
71
|
-
<p class="lede">The main finding, grounded in the available evidence.</p>
|
|
72
|
-
<footer>Data as of … · sources … · how to refresh …</footer>
|
|
73
|
-
</body>
|
|
74
|
-
</html>
|
|
75
46
|
```
|
|
76
47
|
|
|
77
|
-
Lead with the
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
48
|
+
- Lead with the finding; headings state findings. No required sections or emoji chrome.
|
|
49
|
+
- Match the form to the evidence: prose for explanations, cards for metrics,
|
|
50
|
+
tables for comparisons (problems first), timelines for events. Use units,
|
|
51
|
+
baselines and honest precision; every number needs evidence or an explicit
|
|
52
|
+
illustrative calculation. Label uncertainty and stale/unverified claims.
|
|
53
|
+
- Put decisions and evidence first; fold supplementary detail with `<details>`.
|
|
54
|
+
End with data date, checkable sources and refresh instructions, not a recap.
|
|
55
|
+
- No secrets, credentials, internal hostnames or private paths, even in folds:
|
|
56
|
+
private boards can become public with one toggle.
|
|
57
|
+
- Assets belong under `site/`, using relative URLs except the shared stylesheet.
|
|
58
|
+
Illustrate freely with inline SVG or images you draw, render or save into
|
|
59
|
+
`site/`: CSP allows `img-src 'self' data:`. No CDN, external fonts, analytics
|
|
60
|
+
or network fetches; board CSP blocks them.
|
|
61
|
+
- Prefer native links/disclosures. Local JS may sort/filter embedded data;
|
|
62
|
+
keep content readable without JS and hide/disable unavailable controls.
|
|
63
|
+
- Charts must explain something; prefer inline SVG with text/table fallback,
|
|
64
|
+
or a small bundled library only when interaction helps.
|
|
65
|
+
|
|
66
|
+
## Layout and type
|
|
67
|
+
|
|
68
|
+
Inherit the shared responsive canvas: wide desktop, padded/reflowing phone.
|
|
69
|
+
The container is the reading measure: text runs its full width and every
|
|
70
|
+
sibling block shares the same two edges. Do not lock the page, a section or a
|
|
71
|
+
paragraph to a narrower article width unless requested; `.prose` caps the
|
|
72
|
+
column on an ultrawide canvas and belongs on one page-level wrapper, never on
|
|
73
|
+
chapters alternating with full-width blocks.
|
|
74
|
+
|
|
75
|
+
Use the shared system fonts and palette; no downloaded fonts. Body defaults to
|
|
76
|
+
17px/1.7, headings to serif, KPI numbers to body type. Sans-serif headings are an
|
|
77
|
+
optional page choice; code identifiers use monospace, formulas body/math type.
|
|
78
|
+
Preserve contrast and avoid long unbroken identifiers widening grids.
|
|
79
|
+
|
|
80
|
+
Wrap tables in a labeled, keyboard-focusable scrolling region:
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
```html
|
|
83
|
+
<div class="table-scroll" role="region" aria-label="Comparison" tabindex="0">
|
|
84
|
+
<table>…</table>
|
|
85
|
+
</div>
|
|
86
|
+
```
|
|
83
87
|
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
Use native table layout; add a table minimum width only when columns would become
|
|
89
|
+
unreadable on phones. Reuse helpers before adding CSS:
|
|
86
90
|
|
|
87
|
-
|
|
|
88
|
-
| --- | --- |
|
|
89
|
-
| `.lede` / `.hero` | opening answer / tinted opening panel |
|
|
90
|
-
| `.grid` + `.card` + `.kpi` | responsive cards with headline numbers |
|
|
91
|
-
| `.callout` / `.tag` | takeaway or ask / status pill |
|
|
92
|
-
| `.num` | right-aligned tabular numbers |
|
|
93
|
-
| `.bar` | proportion: `style="--v:62%"` |
|
|
94
|
-
| `.split` | two columns, stacking on phones |
|
|
95
|
-
| `.muted` | dates, deltas, units, scope |
|
|
96
|
-
|
|
97
|
-
Status modifiers: `.good` healthy/done, `.warn` attention/pending, `.bad`
|
|
98
|
-
broken/blocked; express status in words too. These colour text, cards,
|
|
99
|
-
callouts, tags and bars; cards and tags also accept `.info` for neutral emphasis.
|
|
100
|
-
All helpers are optional. Custom `<style>` or CSS under `site/` may change
|
|
101
|
-
layout and palette while preserving contrast and phone reflow.
|
|
102
|
-
|
|
103
|
-
## Pick the form from the content
|
|
104
|
-
|
|
105
|
-
| Content | Possible form |
|
|
91
|
+
| Helpers | Purpose |
|
|
106
92
|
| --- | --- |
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
111
|
-
|
|
|
112
|
-
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
##
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
a small bundled library only for needed interaction, with text/table fallback.
|
|
131
|
-
- **Real data:** every figure traces to evidence seen this session; expose
|
|
132
|
-
gaps and uncertainty ("no data since Tue", "~3 weeks", "n=3").
|
|
133
|
-
- **Update in place:** edit the existing board; preserve its URL.
|
|
134
|
-
- Write in the user's language, with short headings and no emoji chrome.
|
|
135
|
-
|
|
136
|
-
## Check the page
|
|
137
|
-
|
|
138
|
-
For new pages or layout/interaction changes, run the checks below; for content
|
|
139
|
-
edits, check the affected content and links. Report actual coverage or gaps.
|
|
140
|
-
|
|
141
|
-
- Phone/desktop, light/dark: readable contrast, no page overflow, tables scroll internally.
|
|
142
|
-
- Keyboard/touch: working links and controls, visible focus, usable targets, text status labels.
|
|
143
|
-
- Assets load without console errors; answer and evidence remain readable with JS off.
|
|
144
|
-
|
|
145
|
-
## If a board needs a build
|
|
146
|
-
|
|
147
|
-
Pier ships no toolchain. If a build is needed, keep sources outside `site/`,
|
|
148
|
-
emit into `site/`, and document install/build commands, output path and data
|
|
149
|
-
sources in `<board>/README.md`. Before editing an existing board, check for
|
|
150
|
-
that README; update sources and rebuild so `site/` stays consistent.
|
|
93
|
+
| `.hero`, `.lede` | opening panel, answer |
|
|
94
|
+
| `.grid`, `.card`, `.kpi` | responsive metric cards |
|
|
95
|
+
| `.split` | two columns that stack |
|
|
96
|
+
| `.prose`, `.table-scroll` | page reading column, scrollable table |
|
|
97
|
+
| `.callout`, `.tag`, `.muted` | takeaway, status, supporting text |
|
|
98
|
+
| `.num`, `.bar` | aligned numbers, proportion (`style="--v:62%"`) |
|
|
99
|
+
|
|
100
|
+
`.good` / `.warn` / `.bad` express status with words as well as color;
|
|
101
|
+
`.info` is available on cards/tags. Custom CSS must retain phone reflow and
|
|
102
|
+
contrast; do not copy shared helpers into pages once the deployed CSS has them.
|
|
103
|
+
|
|
104
|
+
## Verify
|
|
105
|
+
|
|
106
|
+
For layout/interaction changes, check phone/desktop and light/dark: aligned block
|
|
107
|
+
edges, readable line lengths/contrast, no page overflow (including long code),
|
|
108
|
+
tables scroll internally. Exercise keyboard/touch, visible focus and usable
|
|
109
|
+
targets; check assets/console and JS-off readability. Report actual browser
|
|
110
|
+
coverage and gaps; Chromium emulation is not Safari/iOS. For content-only edits,
|
|
111
|
+
check changed content and links.
|
|
112
|
+
|
|
113
|
+
If a build is necessary, keep sources outside `site/`, emit into `site/`, and
|
|
114
|
+
record install/build commands, output path and data sources in `<board>/README.md`.
|
|
115
|
+
Pier ships no build toolchain.
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{$ as e,B as t,E as n,G as r,K as i,M as a,O as o,X as s,Y as c,et as l,it as u,rt as d,w as f}from"./index-CzV2cZEH.js";var p=e=>e.public&&e.token?`/p/${e.slug}-${e.token}/`:`/boards/${e.slug}/`;function m(m,h){let g=[],_=``,v=c(`header`,`pagehead sticky top-[-8px] z-30 max-md:hidden`,a(`Boards`)),y=c(`div`,`px-4 pb-5 pt-1`);m.append(c(`div`,`min-h-0 flex-1 overflow-y-auto`,v,y));async function b(t,n){let r=await u(`/api/boards/${t.slug}`,{public:n},`PATCH`);if(r.ok)return t.token=(await r.json().catch(()=>({}))).token??``,w();_=await e(r,`Could not change ${t.slug}`),await T()}async function x(e){_=await d(`/api/boards/${e}`,`DELETE`,`Could not delete ${e}`)??``,await T()}function S(e){let n=c(`div`,`group flex flex-col gap-1 py-2.5`),r=document.createElement(`a`);r.className=`min-w-0 flex-1 truncate text-[13px] font-medium text-neutral-800 hover:text-indigo-700 max-md:basis-full`,r.href=p(e),r.target=`_blank`,r.rel=`noreferrer`,r.textContent=e.title;let a=c(`div`,`flex flex-wrap items-center gap-x-2 gap-y-1`,r),o=t(``,``,e.public,t=>{e.public=t,b(e,t)});o.className=`flex flex-none cursor-pointer items-center gap-1.5 text-[11.5px] text-neutral-500`,o.append(c(`span`,``,`Public`));let l=f(`Delete`,`flex-none cursor-pointer text-[11.5px] text-neutral-400 opacity-0 transition-opacity hover:text-red-600 group-hover:opacity-100 pointer-coarse:opacity-100`);l.title=`Renames the folder on disk; nothing is erased`,l.onclick=()=>void x(e.slug);let u=i(`flex-none cursor-pointer text-[11.5px] text-neutral-400 opacity-0 transition-opacity hover:text-indigo-600 group-hover:opacity-100 pointer-coarse:opacity-100`,()=>`${location.origin}${p(e)}`);u.title=`Copy the board's link`,a.append(c(`div`,`ml-auto flex flex-none items-center gap-2`,o,u,l)),n.append(a);let d=c(`div`,`flex flex-wrap items-center gap-x-3 gap-y-1 text-[11.5px] text-neutral-500`);d.append(c(`span`,`min-w-0 break-all font-mono text-neutral-400`,e.slug)),e.description&&d.append(c(`span`,`min-w-0 truncate`,e.description)),d.append(c(`span`,`flex-none text-neutral-400`,s(Date.parse(e.updatedAt))));for(let t of e.sessions){let e=f(t.slice(0,8),`flex-none cursor-pointer font-mono text-indigo-600 hover:underline`);e.title=`Open session ${t}`,e.onclick=()=>h(t),d.append(e)}if(e.public&&e.token){let t=c(`span`,`min-w-0 break-all font-mono text-neutral-400`,p(e));t.title=`Public URL — anyone holding this link can read the board, no password`,d.append(t)}return n.append(d),n}let C=[{title:`Public`,hint:`Readable at /p/<slug>-<token>/ by anyone holding the link.`,wanted:!0},{title:`Private`,hint:`Opened from here; the address that opens is a temporary link, good for 8 hours.`,wanted:!1}];function w(){let e=c(`div`,`mx-auto flex w-full max-w-3xl flex-col gap-4`);g.length===0&&e.append(o(`No boards yet. Ask an agent to build one — it writes a folder under ~/.pier/boards.`));for(let{title:t,hint:r,wanted:i}of C){let a=g.filter(e=>e.public===i);a.length!==0&&e.append(n(t,r,c(`div`,`flex flex-col divide-y divide-neutral-200/70`,...a.map(S))))}y.replaceChildren(..._?[c(`p`,`mb-3 text-[13px] text-red-600`,_)]:[],e)}async function T(){let e=await l(`/api/boards`,`Could not load boards`);if(!e.ok)return _=e.error,w();g=e.value,w(),_=``}let E=r(m,()=>void T());return window.addEventListener(`focus`,()=>{E.visible&&T()}),E}export{m as createBoardsView};
|
|
Binary file
|
|
Binary file
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import{G as e,M as t,O as n,W as r,X as i,Y as a,a as o,ct as s,f as c,g as l,h as u,l as d,m as f,mt as p,p as m,pt as ee,q as te,st as h,tt as g,u as _,v as ne,w as re,y as ie}from"./index-CzV2cZEH.js";var ae=[[`path`,{d:`M12 5v14`}],[`path`,{d:`m19 12-7 7-7-7`}]],oe=[[`path`,{d:`M12 22v-6`}],[`path`,{d:`M12 8V2`}],[`path`,{d:`M4 12H2`}],[`path`,{d:`M10 12H8`}],[`path`,{d:`M16 12h-2`}],[`path`,{d:`M22 12h-2`}],[`path`,{d:`m15 19-3-3-3 3`}],[`path`,{d:`m15 5-3 3-3-3`}]],se=[[`path`,{d:`M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z`}]],ce=[[`path`,{d:`M15 6a9 9 0 0 0-9 9V3`}],[`circle`,{cx:`18`,cy:`6`,r:`3`}],[`circle`,{cx:`6`,cy:`18`,r:`3`}]],v=[[`path`,{d:`M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8`}],[`path`,{d:`M3 3v5h5`}]],y=e=>{let t=new Date(e),n=e=>String(e).padStart(2,`0`);return`${t.getFullYear()}-${n(t.getMonth()+1)}-${n(t.getDate())} ${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`},le=e=>[e.subject,e.body,[[e.author,e.email?`<${e.email}>`:``].filter(Boolean).join(` `),e.at?y(e.at):``].filter(Boolean).join(` · `)].filter(Boolean).join(`
|
|
2
|
-
|
|
3
|
-
`),b=null;function x(){b&&(b.remove(),b=null,document.removeEventListener(`pointerdown`,x,!0),window.removeEventListener(`scroll`,x,!0))}function ue(e,t){e.onmouseenter=()=>{x();let n=t();if(!n)return;b=a(`div`,`pointer-events-none fixed z-50 max-w-96 whitespace-pre-wrap rounded-lg bg-neutral-800/95 px-3 py-2 font-sans text-[11.5px] leading-snug text-neutral-100 shadow-lg`),b.textContent=n,document.body.append(b),document.addEventListener(`pointerdown`,x,!0),window.addEventListener(`scroll`,x,!0);let r=e.getBoundingClientRect();b.style.left=`${Math.max(8,Math.min(r.left,window.innerWidth-b.offsetWidth-8))}px`,b.style.top=r.bottom+6+b.offsetHeight>window.innerHeight?`${r.top-b.offsetHeight-6}px`:`${r.bottom+6}px`},e.onmouseleave=x}function S(e,t,n){let r=a(`button`,`flex w-full cursor-pointer items-center gap-1.5 px-2 py-1 text-left hover:bg-neutral-100`,h(ee,`h-3 w-3 text-indigo-600 ${e.value===t?``:`invisible`}`),a(`span`,`inline-flex flex-none items-center gap-1.5 font-mono text-[11.5px] text-neutral-800`,e.label??e.value));return e.subject&&r.append(a(`span`,`min-w-0 truncate text-[11px] text-neutral-400`,e.subject)),e.author&&r.append(a(`span`,`ml-auto flex-none pl-1.5 text-[10.5px] text-neutral-400`,`${e.author} · ${e.at?i(e.at):``}`)),ue(r,()=>le(e)),r.onclick=()=>n(e.value),r}function de(e,t,n,r,i){let{base:o,head:s}=n,l=()=>s&&o===`${s}~1`?s:``,u=l()?`commit`:`compare`,d=t.commits[0],f=e=>[...e?[{value:``,label:`Working tree`,subject:`the files on disk, uncommitted changes included`}]:[],{...d,value:`HEAD`},...t.refs.map(e=>({value:e.name,subject:e.subject})),...t.commits.map(e=>({...e,value:e.hash}))],p=a(`div`,`flex w-[38rem] max-w-full flex-col`);function m(){let e=(e,t)=>{let n=a(`button`,`cursor-pointer rounded-full px-2.5 py-0.5 text-[11.5px] ${u===t?`bg-indigo-50 font-medium text-indigo-700`:`text-neutral-500 hover:bg-neutral-100`}`,e);return n.onclick=()=>{u=t,m()},n},n=(e,t,n,r)=>a(`div`,`flex min-w-0 flex-1 flex-col`,a(`div`,`flex-none px-2 pb-0.5 text-[10.5px] font-semibold uppercase tracking-wide text-neutral-400`,e),a(`div`,`min-h-0 flex-1 overflow-y-auto`,...t.map(e=>S(e,n,r)))),c=u===`compare`?a(`div`,`flex h-72 min-h-0 divide-x divide-neutral-200 pb-1`,n(`base`,f(!1),o,e=>{o=e,m(),r(o,s)}),n(`to`,f(!0),s,e=>{s=e,m(),r(o,s)})):a(`div`,`flex h-72 min-h-0 flex-col overflow-y-auto pb-1`,...t.commits.map(e=>S({value:e.hash,...e},l(),e=>{o=`${e}~1`,s=e,_(),r(o,s)}))),d=a(`div`,`flex-none border-t border-neutral-200`,S({value:i,label:`${i} ↔ Working tree`,subject:`back to the default: the main line's tip vs the files on disk`},o===i&&s===``?i:``,()=>{o=i,s=``,m(),r(o,s)}));d.querySelector(`button > span`).prepend(h(v)),p.replaceChildren(a(`div`,`flex flex-none items-center gap-1 border-b border-neutral-200 px-2 py-1.5`,e(`Compare two points`,`compare`),e(`One commit`,`commit`)),c,...u===`compare`?[d]:[])}m(),c(e,p)}var fe=/\.(png|jpe?g|gif|webp|avif|bmp|svg)$/i,C={A:`text-emerald-600`,M:`text-amber-600`,D:`text-red-600`},pe=30,w=`pier.filesPrefs`,T=50,E=()=>{try{let e=JSON.parse(localStorage.getItem(w)??`{}`);return e&&typeof e==`object`?e:{}}catch{return{}}},me=e=>{let t=e?E()[e]:void 0;return typeof t?.cwd==`string`&&typeof t.base==`string`&&typeof t.head==`string`?{cwd:t.cwd,base:t.base,head:t.head}:null},he=(e,t)=>{let n=E();delete n[e],n[e]=t;let r=Object.keys(n);for(let e of r.slice(0,Math.max(0,r.length-T)))delete n[e];try{localStorage.setItem(w,JSON.stringify(n))}catch(e){console.warn(`Files: could not remember this folder`,e)}},ge=`flex min-w-0 flex-none items-center gap-1.5 rounded-full px-2.5 py-1 font-mono text-[11.5px]`,_e=(e,t=`indigo`)=>re(e,`${ge} cursor-pointer truncate text-left ${t===`indigo`?`bg-indigo-50 font-medium text-indigo-700 hover:bg-indigo-100`:`bg-neutral-100 text-neutral-600 hover:bg-neutral-200`}`);function D(i,c,ee,_){let v=``,y=``,b={branch:null,refs:[],commits:[],worktrees:[]},x=`HEAD`,S=``,w=new Map,T=!0,E=!1,D=[],O=new Set,k=null,A=null,j=null,ve=a(`header`,`pagehead flex-nowrap`),M=a(`div`,`${l} flex-col items-stretch gap-1.5 px-3 py-2.5`),N=a(`div`,`min-h-0 flex-1 overflow-y-auto px-1.5 py-1.5`),ye=a(`aside`,`${ie} w-64 flex-none text-[12.5px] leading-5 max-md:h-1/2 max-md:w-full`,M,N),P=a(`div`,`${l} flex-wrap gap-2 px-4 py-2.5 font-mono text-[11.5px] text-neutral-500`),F=a(`div`,`h-full min-w-0 overflow-auto`),I=a(`div`,`pointer-events-none absolute inset-y-1 right-0.5 z-20 hidden w-2`),be=a(`div`,`relative min-h-0 flex-1`,F,I),xe=a(`section`,`${ie} min-h-0 min-w-0 flex-1`,P,be);i.append(ve,a(`div`,`flex min-h-0 flex-1 gap-3 px-4 pb-4 pt-1 max-md:flex-col`,ye,xe));let L=(e,t)=>`/api/explorer/${e}?${new URLSearchParams({root:v,...t})}`,R=(e,t)=>`/api/fs/${e}?${new URLSearchParams({root:v,...t})}`,z=(e,t=`text-neutral-400`)=>a(`p`,`px-3 py-2 text-[12.5px] ${t}`,e),B=e=>a(`div`,`flex h-full items-center justify-center p-6`,n(e)),Se=()=>T&&b.branch!==null,Ce=e=>{let t=0;for(let n of w.keys())n.startsWith(`${e}/`)&&t++;return t};function we(e,t){let n=new Set(t.map(e=>e.name)),r=e?`${e}/`:``;for(let[e,i]of w){if(i.status!==`D`||!e.startsWith(r))continue;let a=e.slice(r.length).split(`/`)[0];n.has(a)||(n.add(a),t.push({name:a,dir:e.slice(r.length).includes(`/`)}))}let i=t;return Se()&&(i=t.filter(e=>e.dir?Ce(`${r}${e.name}`)>0:w.has(`${r}${e.name}`))),i.sort((e,t)=>Number(t.dir)-Number(e.dir)||e.name.localeCompare(t.name)),i.map(e=>e.dir?Ee(`${r}${e.name}`,e.name):De(`${r}${e.name}`,e.name))}async function Te(e,t){try{let{entries:n}=await g(R(`ls`,{path:t}),`could not list this folder`);e.replaceChildren(...we(t,n)),e.childElementCount||e.append(z(Se()?`No changes.`:`Empty.`))}catch(n){let r=we(t,[]);r.length?e.replaceChildren(...r):e.replaceChildren(z(String(n),`text-red-600`))}}function Ee(e,t){let n=Ce(e),r=[a(`span`,`truncate`,t)];n&&r.push(a(`span`,`ml-auto flex-none rounded-full bg-amber-100/80 px-1.5 font-mono text-[10px] font-semibold text-amber-700`,String(n)));let{el:i,summary:o}=te(``,r);o.className+=` rounded-lg px-2 py-1 transition-colors hover:bg-neutral-100`;let s=a(`div`,`pl-3`);i.append(s);let c=!1,l=()=>{c||(c=!0,s.append(z(`…`)),Te(s,e))};return i.ontoggle=()=>{O[i.open?`add`:`delete`](e),i.open&&l()},((b.branch?T:!e.includes(`/`))&&!E||O.has(e))&&(i.open=!0,l()),i}function V(e){A?.classList.remove(`bg-indigo-50`,`font-medium`,`text-indigo-700`),A=e,e.classList.add(`bg-indigo-50`,`font-medium`,`text-indigo-700`)}function De(e,t){let n=w.get(e)?.status,r=a(`button`,`flex w-full cursor-pointer items-center gap-1.5 rounded-lg py-1 pl-6 pr-2 text-left transition-colors hover:bg-neutral-100`,a(`span`,`truncate`,t));return n&&r.append(a(`span`,`ml-auto flex-none font-mono text-[10.5px] font-semibold ${C[n]??`text-neutral-500`}`,n)),r.title=e,e===k&&V(r),e===j&&(j=null,k=e,V(r),J(e)),r.onclick=()=>{V(r),k=e,J(e)},r}let H=()=>void Te(N,``),U=(e,t)=>[...e?[a(`span`,`flex-none font-mono text-[10.5px] font-semibold text-emerald-600`,`+${e}`)]:[],...t?[a(`span`,`flex-none font-mono text-[10.5px] font-semibold text-red-600`,`−${t}`)]:[]];function W(e,t=!1,n){if(P.classList.toggle(`hidden`,!e),P.classList.toggle(`flex`,!!e),!e)return P.replaceChildren();let i=w.get(e),o=[a(`span`,`min-w-0 truncate`,e)];i&&(o.push(a(`span`,`flex-none font-semibold ${C[i.status]??``}`,i.status)),o.push(...U(i.add,i.del)));let s=a(`div`,`ml-auto flex flex-none items-center gap-2`);if(n&&s.append(n),t){let t=document.createElement(`a`);t.className=`btn flex-none text-[12px]`,t.href=R(`file`,{path:e}),t.download=r(e),t.textContent=`Download`,s.append(t)}P.replaceChildren(...o,s)}function Oe(e){let t=0;for(;t<e.length;){if(e[t].tone!==`del`){t++;continue}let n=t;for(;t<e.length&&e[t].tone===`del`;)t++;let r=t;for(;t<e.length&&e[t].tone===`add`;)t++;let i=Math.min(r-n,t-r);for(let t=0;t<i;t++){let i=e[n+t],a=e[r+t],o=Math.min(i.text.length,a.text.length),s=0;for(;s<o&&i.text[s]===a.text[s];)s++;let c=0;for(;c<o-s&&i.text[i.text.length-1-c]===a.text[a.text.length-1-c];)c++;s+c!==0&&(i.mark=[s,i.text.length-c],a.mark=[s,a.text.length-c])}}}let G=Ie();function K(){I.classList.add(`hidden`),I.replaceChildren(),G.set([],[])}let ke=0,q=e=>e===ke;function J(e){let t=++ke;return w.has(e)?Le(e,t):Ae(e,t)}async function Ae(e,t){if(!q(t))return;K();let n=R(`file`,{path:e}),r=a(`div`,`min-w-0`);if(F.classList.remove(`flex`,`flex-col`),W(e,!0),F.replaceChildren(r),fe.test(e)){let e=a(`img`,`max-w-full p-4`);e.src=n,e.onerror=()=>r.replaceChildren(z(`Could not load image.`,`text-red-600`)),r.append(e);return}if(e.toLowerCase().endsWith(`.pdf`)){let e=document.createElement(`iframe`);e.className=`h-full w-full`,e.src=n,F.replaceChildren(e),F.classList.add(`flex`,`flex-col`);return}r.append(z(`…`));let i=await fetch(n);if(!q(t))return;if(!i.ok){let{error:e}=await i.json().catch(()=>({}));r.replaceChildren(z(e??`HTTP ${i.status}`,`text-red-600`));return}if(!(i.headers.get(`content-type`)??``).startsWith(`text/plain`)){r.replaceChildren(z(`Binary file — use Download.`));return}let o=f(await i.text());if(!q(t))return;let s=await u(e);q(t)&&r.replaceChildren(m(o,s))}function je(e){let t=[],n=0,r=0,i=!1,a=e.split(`
|
|
4
|
-
`);a.at(-1)===``&&a.pop();for(let e of a){let a=/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(e);if(a){i&&t.push({nums:[``,``],text:`⋯`,tone:``}),[n,r]=[Number(a[1]),Number(a[2])],i=!0;continue}!i||e.startsWith(`\\`)||(e.startsWith(`+`)?t.push({nums:[``,r++],text:e.slice(1),tone:`add`}):e.startsWith(`-`)?t.push({nums:[n++,``],text:e.slice(1),tone:`del`}):t.push({nums:[n++,r++],text:e.slice(1),tone:``}))}return t}function Me(e){let t=[];for(let n=0;n<e.length;n++){let r=e[n].tone;if(!r)continue;let i=t.at(-1);i&&i.end===n-1?(i.end=n,(r===`add`&&i.tone===`del`||r===`del`&&i.tone===`add`)&&(i.tone=`mixed`)):t.push({start:n,end:n,tone:r})}return t}let Ne={add:`bg-emerald-400`,del:`bg-red-400`,mixed:`bg-amber-400`};function Pe(e,t,n){I.replaceChildren(...e.map(e=>{let r=a(`button`,`map-mark pointer-events-auto absolute left-1/2 w-1 -translate-x-1/2 cursor-pointer rounded-full ${Ne[e.tone]}`);return r.style.top=`${e.start/t*100}%`,r.style.height=`${Math.max(.4,(e.end-e.start+1)/t*100)}%`,r.title=`line ${Fe(n,e.start)}`,r.onclick=()=>n[e.start].scrollIntoView({block:`center`}),r})),I.classList.remove(`hidden`)}let Fe=(e,t)=>e[t]?.querySelector(`span`)?.textContent?.trim()||String(t+1);function Ie(){let e=[],t=[],n=-1,r=a(`span`,`flex-none text-[10.5px] text-neutral-400`),s=i=>{e.length&&(n=(n+i+e.length)%e.length,t[e[n].start].scrollIntoView({block:`center`}),r.textContent=`${n+1}/${e.length}`)},c=()=>e.length>0&&!i.classList.contains(`hidden`),l=(e,t,n,r)=>{let i=a(`button`,`icon-btn flex-none`,h(e));return i.onclick=()=>s(t),o(i,r,n,()=>s(t),c),i};return{el:a(`div`,`flex flex-none items-center gap-1`,r,l(p,-1,`Previous change`,[`p`,`k`]),l(ae,1,`Next change`,[`n`,`j`])),set(i,a){e=i,t=a,n=-1,r.textContent=`${i.length} change${i.length===1?``:`s`}`}}}async function Le(e,t){K(),F.classList.remove(`flex`,`flex-col`);let n=w.get(e)?.status!==`D`;W(e,n),F.replaceChildren(z(`…`));try{let{diff:r}=await g(L(`diff`,{base:x,head:S,file:e,context:`99999`}),`could not read this diff`);if(!q(t))return;if(!r||/^Binary files /m.test(r))return Ae(e,t);let i=je(r);Oe(i);let a=await u(e);if(!q(t))return;let o=m(i,a),s=[...o.children],c=Me(i);G.set(c,s),W(e,n,G.el),F.replaceChildren(o),Pe(c,i.length,s)}catch(n){if(!q(t))return;W(e),F.replaceChildren(z(String(n),`text-red-600`))}}let Y=()=>S&&x===`${S}~1`?S:``,Re=()=>b.commits.find(e=>e.hash===Y())?.subject??``,ze=()=>Y()?`${Y()} — ${Re()||`commit`}`:`${x} ↔ ${S||`working tree`}`,Be=(e,t,n,r)=>{let i=a(`button`,`icon-btn ${n?`bg-indigo-50 text-indigo-700 hover:bg-indigo-100 hover:text-indigo-700`:``}`);return i.append(h(e)),i.title=t,i.setAttribute(`aria-label`,t),i.setAttribute(`aria-pressed`,String(n)),i.onclick=r,i};function X(){let e=Be(se,`Show only files that differ`,T,()=>{T=!T,X(),H()}),t=Be(oe,E?`Restore folders`:`Collapse all folders`,E,()=>{if(E=!E,E)D=[...O],O.clear();else for(let e of D)O.add(e);X(),H()});if(!b.branch){M.replaceChildren(a(`div`,`flex items-center gap-1.5`,a(`span`,`min-w-0 flex-1 text-[11px] leading-snug text-neutral-400`,`Not a git repository — nothing to compare.`),t));return}let n=re(ze(),`${ne} font-mono`);if(Y()){let e=b.commits.find(e=>e.hash===Y());ue(n,()=>e?le(e):``)}else n.title=`Choose what to diff`;n.onclick=async()=>{await We(),de(n,b,{base:x,head:S},(e,t)=>{x=e,S=t,Z()},$())};let[r,i]=[0,0];for(let e of w.values())r+=e.add,i+=e.del;M.replaceChildren(n,a(`div`,`flex items-center gap-1.5 pl-0.5`,a(`span`,`text-[11px] text-neutral-400`,`${w.size} changed file${w.size===1?``:`s`}`),...U(r,i),a(`span`,`ml-auto`),e,t))}let Ve=()=>{y&&v&&he(y,{cwd:v,base:x,head:S})},He=e=>{let t=e.replace(/~\d+$/,``);return!t||t===`HEAD`||b.refs.some(e=>e.name===t)||b.commits.some(e=>e.hash===t)};async function Ue(){if(w=new Map,!b.branch)return;let{files:e}=await g(L(`diff`,{base:x,head:S}),`could not read what changed`);for(let t of e)w.set(t.path,{status:t.status,add:t.add,del:t.del});if(w.size<=pe)for(let e of w.keys()){let t=e.split(`/`);for(let e=1;e<t.length;e++)O.add(t.slice(0,e).join(`/`))}}async function Z(){Ve();try{await Ue()}catch(e){X(),N.replaceChildren(z(String(e),`text-red-600`));return}X(),H(),k&&J(k)}function Q(){let e=_e(``,`neutral`);e.className+=` max-w-72`,e.append(a(`span`,`truncate max-md:hidden`,v||`Choose a folder…`),a(`span`,`truncate md:hidden`,v?r(v):`Choose a folder…`)),e.title=v?`${v} — switch folder`:`Choose a folder`,e.onclick=()=>d(e,b.worktrees.map(e=>({path:e.path,...e.branch?{hint:e.branch}:{}})),v||void 0,ee);let n=a(`button`,`icon-btn ml-auto`,h(s));n.type=`button`,n.title=`Close Files`,n.setAttribute(`aria-label`,`Close Files`),n.onclick=_;let i=b.branch?[a(`span`,`${ge} bg-neutral-100 text-neutral-600`,h(ce),a(`span`,`truncate`,b.branch))]:[];ve.replaceChildren(t(`Files`),e,...i,n)}async function We(){if(v)try{b=await g(L(`git`,{}),`could not read this repository`),Q()}catch(e){F.replaceChildren(B(String(e)))}}let $=()=>b.refs.some(e=>e.name===`main`)?`main`:b.refs.some(e=>e.name===`master`)?`master`:`HEAD`;async function Ge(e){if(Q(),k=null,A=null,O.clear(),j=e||null,e){T=!1;let t=e.split(`/`);for(let e=1;e<t.length;e++)O.add(t.slice(0,e).join(`/`))}if(K(),F.classList.remove(`flex`,`flex-col`),W(),F.replaceChildren(B(`Select a file.`)),!v){M.replaceChildren(),N.replaceChildren(),F.replaceChildren(B(`No folder yet — pick one from the chip above.`));return}try{b=await g(L(`git`,{}),`could not read this repository`)}catch(e){b={branch:null,refs:[],commits:[],worktrees:[]},F.replaceChildren(B(String(e)))}let t=me(y),n=t&&t.cwd===v&&He(t.base)&&He(t.head)?t:null;x=n?.base??$(),S=n?.head??``,Q(),await Z()}return e(i,(e,t)=>{let n=c(),r=n?.id??``,i=new URLSearchParams(t).get(`select`)??void 0,a=e?.startsWith(`/`)?e:(r===y?v:``)||me(r)?.cwd||n?.cwd||``;if(a===v&&r===y&&N.childElementCount&&(!i||i===k)){Q(),We().then(Z);return}y=r,v=a,Ge(i)},()=>{K(),F.classList.remove(`flex`,`flex-col`),W(),F.replaceChildren(B(`Select a file.`))})}export{D as createExplorerView};
|
|
Binary file
|
|
Binary file
|