@timqi/pier 0.1.10 → 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.
Files changed (66) hide show
  1. package/dist/boards/pier.css +37 -22
  2. package/dist/channels/config.js +40 -2
  3. package/dist/channels/control.js +10 -3
  4. package/dist/channels/conversations.js +9 -0
  5. package/dist/channels/lark.js +3 -0
  6. package/dist/channels/panel.js +3 -6
  7. package/dist/channels/routes.js +16 -1
  8. package/dist/channels/slack.js +3 -0
  9. package/dist/channels/types.js +1 -0
  10. package/dist/main.js +1 -1
  11. package/dist/web/public/assets/{activity-C9PhqKVX.js → activity-CJtsniqS.js} +2 -2
  12. package/dist/web/public/assets/activity-CJtsniqS.js.br +0 -0
  13. package/dist/web/public/assets/activity-CJtsniqS.js.gz +0 -0
  14. package/dist/web/public/assets/boards-DyQtNqgv.js +1 -0
  15. package/dist/web/public/assets/boards-DyQtNqgv.js.br +0 -0
  16. package/dist/web/public/assets/boards-DyQtNqgv.js.gz +0 -0
  17. package/dist/web/public/assets/explorer-BQYNIR0V.js +4 -0
  18. package/dist/web/public/assets/explorer-BQYNIR0V.js.br +0 -0
  19. package/dist/web/public/assets/explorer-BQYNIR0V.js.gz +0 -0
  20. package/dist/web/public/assets/index-CLNTSmYN.js +85 -0
  21. package/dist/web/public/assets/index-CLNTSmYN.js.br +0 -0
  22. package/dist/web/public/assets/index-CLNTSmYN.js.gz +0 -0
  23. package/dist/web/public/assets/index-DiHj0w1i.css +2 -0
  24. package/dist/web/public/assets/index-DiHj0w1i.css.br +0 -0
  25. package/dist/web/public/assets/index-DiHj0w1i.css.gz +0 -0
  26. package/dist/web/public/assets/{runs-BNv9MSL2.js → runs-tYH577mL.js} +1 -1
  27. package/dist/web/public/assets/runs-tYH577mL.js.br +0 -0
  28. package/dist/web/public/assets/runs-tYH577mL.js.gz +0 -0
  29. package/dist/web/public/assets/settings-B42zHcpu.js +5 -0
  30. package/dist/web/public/assets/settings-B42zHcpu.js.br +0 -0
  31. package/dist/web/public/assets/settings-B42zHcpu.js.gz +0 -0
  32. package/dist/web/public/assets/{task-runs-I1jLSvYq.js → task-runs-COIFbwwz.js} +2 -2
  33. package/dist/web/public/assets/task-runs-COIFbwwz.js.br +0 -0
  34. package/dist/web/public/assets/task-runs-COIFbwwz.js.gz +0 -0
  35. package/dist/web/public/assets/tasks-D1dGhkTZ.js +4 -0
  36. package/dist/web/public/assets/tasks-D1dGhkTZ.js.br +0 -0
  37. package/dist/web/public/assets/tasks-D1dGhkTZ.js.gz +0 -0
  38. package/dist/web/public/index.html +2 -2
  39. package/dist/web/public/index.html.br +0 -0
  40. package/dist/web/public/index.html.gz +0 -0
  41. package/package.json +1 -1
  42. package/skills/pier-boards/SKILL.md +89 -124
  43. package/dist/web/public/assets/activity-C9PhqKVX.js.br +0 -0
  44. package/dist/web/public/assets/activity-C9PhqKVX.js.gz +0 -0
  45. package/dist/web/public/assets/boards-YW0uAwJI.js +0 -1
  46. package/dist/web/public/assets/boards-YW0uAwJI.js.br +0 -0
  47. package/dist/web/public/assets/boards-YW0uAwJI.js.gz +0 -0
  48. package/dist/web/public/assets/explorer-BNm0Cr51.js +0 -4
  49. package/dist/web/public/assets/explorer-BNm0Cr51.js.br +0 -0
  50. package/dist/web/public/assets/explorer-BNm0Cr51.js.gz +0 -0
  51. package/dist/web/public/assets/index-Bfy7U1wv.js +0 -85
  52. package/dist/web/public/assets/index-Bfy7U1wv.js.br +0 -0
  53. package/dist/web/public/assets/index-Bfy7U1wv.js.gz +0 -0
  54. package/dist/web/public/assets/index-sQWFDUPu.css +0 -2
  55. package/dist/web/public/assets/index-sQWFDUPu.css.br +0 -0
  56. package/dist/web/public/assets/index-sQWFDUPu.css.gz +0 -0
  57. package/dist/web/public/assets/runs-BNv9MSL2.js.br +0 -0
  58. package/dist/web/public/assets/runs-BNv9MSL2.js.gz +0 -0
  59. package/dist/web/public/assets/settings-Dxxux4Un.js +0 -5
  60. package/dist/web/public/assets/settings-Dxxux4Un.js.br +0 -0
  61. package/dist/web/public/assets/settings-Dxxux4Un.js.gz +0 -0
  62. package/dist/web/public/assets/task-runs-I1jLSvYq.js.br +0 -0
  63. package/dist/web/public/assets/task-runs-I1jLSvYq.js.gz +0 -0
  64. package/dist/web/public/assets/tasks-DqrNu2Tz.js +0 -4
  65. package/dist/web/public/assets/tasks-DqrNu2Tz.js.br +0 -0
  66. package/dist/web/public/assets/tasks-DqrNu2Tz.js.gz +0 -0
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timqi/pier",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "A self-hosted workspace for coding agents: web workbench and IM channels in front of Pi sessions",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": "github:timqi/pier",
@@ -1,150 +1,115 @@
1
1
  ---
2
2
  name: pier-boards
3
- description: Publish a Board — a folder of static HTML Pier serves at a stable URL. Read before building any page-shaped deliverable (report, digest, dashboard) or editing an existing 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
- # Building a Pier board
6
+ # Boards
7
7
 
8
- A **board** lives in the exact boards folder named under "This Pier instance"
9
- in `<pier>/AGENTS.md`; do not assume `~/.pier`. Only `<board>/site/` is served.
10
- Boards survive sessions; any session may read or update them.
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
- ## Create one
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
- - `slug`: `[a-z0-9][a-z0-9-]{0,63}`; keep it short and stable, without random suffixes.
30
- - `description`: Console list text, understandable without this conversation.
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
- For publishing, preserve the manifest's `token` or add eight hex characters
41
- from `openssl rand -hex 4`; never invent or reuse another board's token.
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
- Use the instance address from `<pier>/AGENTS.md` plus:
29
+ Return one bare URL using the configured instance address:
44
30
 
45
- | Visibility | Path |
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
- Return **one bare URL**, never both, Markdown link labels or filesystem paths.
51
- If no address is configured, give the path and point to Console → Settings;
52
- never guess a host. For a publish-only request, set `public: true` and a token
53
- if missing, then return only the public URL; no page verification or narration.
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
- ## Writing the page
39
+ ## Page
56
40
 
57
- Let the question and evidence determine layout; no sections are required.
58
- Use semantic HTML without a framework. Adapt this shell's language and content:
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 verdict; headings state findings ("Payments alone is degraded").
78
- Numbers carry units and baselines ("142 ms p95, was 120", "12 of 40"), without
79
- false precision. End without filler or a closing summary; put the data
80
- timestamp, sources and refresh instructions in the footer.
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
- ## What `pier.css` gives you
82
+ ```html
83
+ <div class="table-scroll" role="region" aria-label="Comparison" tabindex="0">
84
+ <table>…</table>
85
+ </div>
86
+ ```
83
87
 
84
- Classless defaults include readable prose, responsive widths, dark mode,
85
- scrolling zebra tables and print styles. Override widths only as needed.
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
- | Class | Use it for |
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
- | One finding or 1–2 values | sentence; if nothing changed, say so and stop |
108
- | Headline metrics | KPI cards with deltas; progress includes the total |
109
- | Required action | prominent ask with deadline |
110
- | States, rankings or shared comparison criteria | table; worst states first |
111
- | Alternatives needing separate explanations | parallel sections; recommendation first |
112
- | Events / trends | dated list / endpoints and delta; chart when shape explains the finding |
113
- | Large datasets or raw output | aggregate, show relevant rows, fold the rest with a count; trim code/logs |
114
-
115
- Repeat forms for comparable items; vary them when information changes. Put
116
- findings and actions first, then evidence; fold only supplementary detail.
117
-
118
- ## Rules
119
-
120
- - **No secrets:** no tokens, API keys, credentials, internal hostnames or
121
- private paths in page content, including folds and code samples; private
122
- boards can become public with one Console toggle.
123
- - **Self-contained:** assets live under `site/` with relative paths, except
124
- the shipped stylesheet; no CDN, external fonts, analytics or `fetch()`
125
- (blocked by the board CSP).
126
- - **Interaction:** prefer `<details>` and anchors; local JS may sort, filter or
127
- inspect embedded data, without network calls or server runtime. With JS off,
128
- keep the answer and evidence readable; hide or disable unavailable controls.
129
- - **Charts:** use when data shape explains the finding; prefer inline SVG,
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.
@@ -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-Bfy7U1wv.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};
@@ -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-Bfy7U1wv.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};