@lensmap/language-server 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lensmap contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,38 @@
1
- # Temporary Holding Version
1
+ # @lensmap/language-server
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Experimental Lensmap stdio language server and authenticated loopback browser
4
+ preview. Includes its compiled browser assets; npm installs the document engine
5
+ and other runtime dependencies before startup. No workspace checkout, pnpm,
6
+ Rust, runtime dependency download or separate web server is needed to run it.
7
+
8
+ Requires Node 24.16 or newer. ESM only. The API and document format are
9
+ experimental. Native host integration is currently qualified on Linux/X11;
10
+ other platforms are not yet qualified. Automatic browser launching uses
11
+ `xdg-open`; `--no-open` leaves opening the preview URL to the host/client.
12
+
13
+ ```sh
14
+ npm install --global @lensmap/language-server@next
15
+ lensmap-lsp --help
16
+ lensmap-lsp --configure-zed --zed /usr/bin/zeditor
17
+ ```
18
+
19
+ The last command **prints** settings, without modifying your editor. Use your
20
+ verified Zed executable path. Keep the Node installation and package path stable,
21
+ and regenerate settings if either changes. Zed still requires the separately
22
+ installed Lensmap **dev extension** from the source repository's `editors/zed`
23
+ directory; the npm package does not install or register an editor extension.
24
+
25
+ The editor launches `lensmap-lsp --stdio`. Its stdout is reserved for LSP; it is
26
+ not a standalone saved-file viewer or persistent background web daemon. Attach a
27
+ `.lensmap` buffer and invoke **Lensmap: Open preview** through editor code
28
+ actions. The server defaults to its package-local built viewer. `--assets PATH`
29
+ remains available for development/custom assets.
30
+
31
+ Preview URLs and API tokens are private per-run credentials. Do not log, paste
32
+ or retain them in traces. The server binds to loopback, validates Host/Origin,
33
+ and limits source access to attached buffers. Content writes use revision checks
34
+ and live-buffer readback, **not atomic compare-and-swap**.
35
+
36
+ See [Zed setup, API and limitations](https://github.com/nicodoggie/lensmap/blob/main/docs/zed-browser.md).
37
+ The library export exposes `startServer` and its TypeScript declarations; custom
38
+ hosts supply their own `assets` and host callbacks. MIT licensed; see `LICENSE`.
@@ -0,0 +1,2 @@
1
+ export type { RevealTarget, ServerHandle, ServerOptions } from "./server.js";
2
+ export { startServer } from "./server.js";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { startServer } from "./server.js";
package/dist/main.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/main.js ADDED
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { parseArgs } from "node:util";
6
+ import { startServer } from "./server.js";
7
+ const { values } = parseArgs({
8
+ options: {
9
+ stdio: { type: "boolean" },
10
+ assets: { type: "string" },
11
+ zed: { type: "string", default: "zeditor" },
12
+ "zed-data-dir": { type: "string" },
13
+ "no-open": { type: "boolean" },
14
+ "configure-zed": { type: "boolean" },
15
+ help: { type: "boolean", short: "h" },
16
+ },
17
+ });
18
+ const assets = resolve(values.assets ?? resolve(import.meta.dirname, "preview"));
19
+ if (values.help) {
20
+ process.stdout.write("Lensmap experimental language server + loopback preview\n\nUsage: lensmap-lsp --stdio [--assets PATH] [--zed EXECUTABLE]\n --assets PATH Override the bundled browser preview assets\n --no-open Return preview links without launching a browser\n --zed-data-dir DIR Target a private Zed profile for source reveal\n --configure-zed Print workspace settings for the local dev extension\n\nInstalled packages include the browser preview. For source checkouts, run pnpm build.\nPreview URLs/tokens are private per-run credentials; retrieve them through\nLensmap's code actions or lensmap.session LSP command.\nEditor writes use revision checks/readback, not atomic compare-and-swap.\n");
21
+ }
22
+ else if (values["configure-zed"]) {
23
+ process.stdout.write(`${JSON.stringify({
24
+ file_types: { YAML: ["lensmap"] },
25
+ languages: { YAML: { language_servers: ["lensmap", "..."] } },
26
+ lsp: {
27
+ lensmap: {
28
+ binary: {
29
+ path: process.execPath,
30
+ arguments: [
31
+ fileURLToPath(import.meta.url),
32
+ "--stdio",
33
+ "--assets",
34
+ assets,
35
+ "--zed",
36
+ values.zed,
37
+ ],
38
+ },
39
+ },
40
+ },
41
+ }, null, 2)}\n`);
42
+ }
43
+ else {
44
+ const launch = (executable, args) => new Promise((done, reject) => {
45
+ const child = spawn(executable, args, { stdio: "ignore" });
46
+ child.once("error", () => reject(new Error("Host opener could not start.")));
47
+ child.once("exit", (code) => code === 0 ? done() : reject(new Error("Host opener failed.")));
48
+ });
49
+ const reveal = async ({ uri, range, source }) => {
50
+ if (!uri.startsWith("file:"))
51
+ throw new Error("This host reveals attached file documents only.");
52
+ const line = source.split("\n")[range.start.line] ?? "";
53
+ const column = Buffer.byteLength(line.slice(0, range.start.character), "utf8") + 1;
54
+ const location = `${fileURLToPath(uri)}:${range.start.line + 1}:${column}`;
55
+ await launch(values.zed ?? "zeditor", [
56
+ ...(values["zed-data-dir"]
57
+ ? ["--user-data-dir", values["zed-data-dir"]]
58
+ : []),
59
+ "--existing",
60
+ location,
61
+ ]);
62
+ };
63
+ try {
64
+ const server = await startServer({
65
+ input: process.stdin,
66
+ output: process.stdout,
67
+ assets,
68
+ reveal,
69
+ open: values["no-open"]
70
+ ? undefined
71
+ : async (url) => {
72
+ if (process.platform !== "linux")
73
+ throw new Error("Browser launching is currently qualified on Linux only; use the returned preview URL.");
74
+ await launch("xdg-open", [url]);
75
+ },
76
+ });
77
+ process.stderr.write(`Lensmap ready at ${server.origin}; use the Open preview code action.\n`);
78
+ const stop = () => {
79
+ void server.close().finally(() => {
80
+ process.stdin.destroy();
81
+ });
82
+ };
83
+ process.once("SIGTERM", stop);
84
+ process.once("SIGINT", stop);
85
+ }
86
+ catch {
87
+ process.stderr.write("Lensmap could not start. Check the preview asset directory; rebuild source checkouts with pnpm build or reinstall the npm package. See --help.\n");
88
+ process.exitCode = 1;
89
+ }
90
+ }
@@ -0,0 +1,215 @@
1
+ import{a as e,d as t,f as n,h as r,i,l as a,m as o,n as s,o as c,p as l,r as u,s as d,t as f,u as p}from"./styles-CWsrvT4d.js";var m=r(),h=o();function g(e){try{let n=new t(`playground`,e).snapshot();return!n.valid||!n.graph?{ok:!1,error:n.diagnostics.map(e=>e.message).join(`
2
+ `)}:{ok:!0,graph:p(n.graph)}}catch(e){return{ok:!1,error:e instanceof Error?e.message:`The document could not be parsed.`}}}var _=c();function v({graph:t,thoughtHref:r,active:a,onFocus:o,onFragment:s,onActivatePortal:c,source:u,applied:d,error:f,status:p,onSourceChange:m,onApply:h,onLoadSample:g}){let v=t.edges.filter(e=>e.from===a?.key);return(0,_.jsxs)(`aside`,{className:`notes`,id:`lensmap-sidebar`,"aria-label":`Sidebar`,children:[(0,_.jsxs)(`div`,{className:`note-eyebrow`,children:[(0,_.jsx)(`span`,{children:`THOUGHTS & SOURCE`}),(0,_.jsx)(`span`,{children:`v1 / incomplete`})]}),(0,_.jsxs)(`details`,{className:`sidebar-section`,open:!0,children:[(0,_.jsx)(`summary`,{children:`Selected thought`}),(0,_.jsxs)(`div`,{className:`note-content`,children:[a?(0,_.jsx)(e,{components:{h1:({children:e})=>(0,_.jsx)(`h2`,{children:e}),a:({href:e,children:t})=>(0,_.jsx)(`a`,{href:e,onClick:t=>{e?.startsWith(`#`)&&(t.preventDefault(),s(e.slice(1)))},children:t})},children:a.markdown||(a.unresolved?`# Unresolved thought
3
+
4
+ This thought has not been named yet.`:a.title)}):(0,_.jsx)(`p`,{children:`No thought selected.`}),a&&(0,_.jsx)(`a`,{href:r??`#${encodeURIComponent(l(t.nodes).get(a.key)??a.key)}`,onClick:e=>{e.preventDefault(),s(encodeURIComponent(l(t.nodes).get(a.key)??a.key))},children:`Link to this thought`}),a?.destination&&(0,_.jsxs)(`div`,{className:`portal-actions`,children:[(0,_.jsx)(`p`,{children:a.destination.url}),(0,_.jsxs)(`button`,{type:`button`,onClick:()=>c?.(a),disabled:!c,children:[`Open`,` `,a.destination.kind===`map_link`?`destination map`:`external link`]})]})]})]}),(0,_.jsxs)(`details`,{className:`sidebar-section outgoing`,open:!0,children:[(0,_.jsx)(`summary`,{children:`Explicit connections`}),v.map(e=>{let r=t.nodes.find(t=>t.key===e.to);return r&&(0,_.jsxs)(`button`,{type:`button`,onClick:()=>o(e.to),children:[(0,_.jsxs)(`span`,{children:[n(e.type,`outgoing`),` · `,i(r)]}),(0,_.jsx)(`span`,{"aria-hidden":`true`,children:`↗`})]},e.key)}),!v.length&&(0,_.jsx)(`p`,{children:`No outgoing connections yet.`})]}),(0,_.jsxs)(`details`,{className:`sidebar-section outgoing`,open:!0,children:[(0,_.jsx)(`summary`,{children:`Incoming connections`}),t.edges.filter(e=>e.to===a?.key).map(e=>{let r=t.nodes.find(t=>t.key===e.from);return r&&(0,_.jsxs)(`button`,{type:`button`,onClick:()=>o(r.key),children:[n(e.type,`incoming`),` · `,i(r)]},e.key)})]}),(0,_.jsxs)(`details`,{className:`sidebar-section playground`,children:[(0,_.jsx)(`summary`,{children:`YAML playground`}),(0,_.jsxs)(`p`,{className:`editor-help`,id:`source-help`,children:[`Experimental `,(0,_.jsx)(`code`,{children:`version: v1`}),`. This is an incomplete schema, not a frozen format. Write thoughts first; positions and IDs are optional.`]}),(0,_.jsxs)(`form`,{onSubmit:e=>{e.preventDefault(),h()},children:[(0,_.jsx)(`label`,{className:`source-label`,htmlFor:`yaml-source`,children:`YAML source`}),(0,_.jsx)(`textarea`,{id:`yaml-source`,"aria-describedby":`source-help source-lifetime`,spellCheck:!1,value:u,onChange:e=>m(e.target.value)}),(0,_.jsxs)(`div`,{className:`editor-actions`,children:[(0,_.jsx)(`button`,{type:`submit`,children:`Apply YAML`}),(0,_.jsx)(`button`,{type:`button`,onClick:g,children:`Load sample`})]}),(0,_.jsx)(`p`,{className:`editor-state`,children:u===d?`Draft matches the map.`:`Unapplied changes — the map still shows the last valid document.`}),f&&(0,_.jsx)(`pre`,{className:`source-error`,role:`alert`,children:f}),(0,_.jsx)(`p`,{className:`editor-state`,role:`status`,children:p})]}),(0,_.jsx)(`p`,{className:`editor-help`,id:`source-lifetime`,children:`Drafts stay here while you navigate or collapse sections. Reloading the page discards them; saving and editor language tooling are not implemented yet.`})]})]})}var y=`# Lensmap v1 is incomplete and experimental. This sample is editable.
5
+ version: v1
6
+ nodes:
7
+ - id: ferry
8
+ full_text: |
9
+ # Night ferry
10
+
11
+ What would make a little night ferry worth taking? Start with the journey, not the timetable.
12
+
13
+ The [tide window](#tides) is a prose reference: following it does **not** add a line to the map.
14
+
15
+ - A dependable crossing
16
+ - A warm place to wait
17
+ - Room for unexpected stories
18
+
19
+ connections:
20
+ - full_text: |
21
+ # Weather
22
+
23
+ Rain changes the crossing, but it also changes how people wait. A roof can matter as much as a faster boat.
24
+
25
+ connections:
26
+ - id: tides
27
+ full_text: |
28
+ # Tide window
29
+
30
+ A timetable is a promise made to a moving shoreline.
31
+
32
+ **Question:** can the last departure move without leaving someone behind?
33
+
34
+ Return to the [night ferry](#ferry).
35
+
36
+ connections:
37
+ - full_text: |
38
+ # Tide tables
39
+
40
+ Compare the published prediction with the water at the actual landing.
41
+
42
+ - full_text: |
43
+ # Sandbar markers
44
+
45
+ A route that was clear last month may have moved after the storms.
46
+
47
+ - full_text: |
48
+ # Spring tides
49
+
50
+ The largest tidal range changes both the channel and the boarding ramp.
51
+
52
+ - full_text: |
53
+ # Distant tide gauge
54
+
55
+ An outer-harbor observation may explain a delay that is invisible at the pier.
56
+
57
+ - full_text: |
58
+ # A small forecast
59
+
60
+ A plain-language board at the pier. No need to make a traveller interpret a marine chart.
61
+
62
+ connections:
63
+ - node_id: harbor
64
+ - full_text: |
65
+ # Wind flags
66
+
67
+ A small visual signal can communicate more quickly than a paragraph of forecasts.
68
+
69
+ - full_text: |
70
+ # A squall line
71
+
72
+ The approaching weather matters more than the rain already overhead.
73
+
74
+ - full_text: |
75
+ # Offshore reports
76
+
77
+ Ask the boats beyond the headland what the sheltered pier cannot show.
78
+
79
+ - full_text: |
80
+ # Beyond the headland
81
+
82
+ The farthest observation belongs to the same crossing, even when it sits at the edge of attention.
83
+
84
+ - full_text: |
85
+ # Who crosses?
86
+
87
+ The cook going home. The early market seller. Someone who just wants to see the lights from the water.
88
+
89
+ connections:
90
+ - full_text: |
91
+ # A step-free pier
92
+
93
+ Access is part of the route, not something added after the boat is chosen.
94
+
95
+ connections:
96
+ - node_id: harbor
97
+ - full_text: |
98
+ # Ramp gradient
99
+
100
+ Check the boarding angle at low water, not only at a convenient midday tide.
101
+
102
+ - full_text: |
103
+ # Tactile wayfinding
104
+
105
+ The path should still make sense when a sign is difficult to see.
106
+
107
+ - full_text: |
108
+ # Late bus connection
109
+
110
+ A step-free boat is not enough if the last accessible bus leaves before it docks.
111
+
112
+ - full_text: |
113
+ # The last mile home
114
+
115
+ Follow the journey all the way from the landing to a traveller's own door.
116
+
117
+ - full_text: |
118
+ # Stories aboard
119
+
120
+ What do regular passengers notice that a route planner would miss?
121
+
122
+ > The crossing begins before you board.
123
+
124
+ connections:
125
+ - node_id: archive
126
+ - full_text: |
127
+ # Market mornings
128
+
129
+ The early crossing carries baskets, unfinished conversations, and a different rhythm.
130
+
131
+ - full_text: |
132
+ # Familiar faces
133
+
134
+ A regular passenger may notice an absence before anyone notices a missed ticket.
135
+
136
+ - full_text: |
137
+ # Songs at the pier
138
+
139
+ Keep a little room in the notes for things that are not operational requirements.
140
+
141
+ - full_text: |
142
+ # A crossing remembered
143
+
144
+ Someone who moved away still remembers which side of the boat caught the evening light.
145
+
146
+ - id: harbor
147
+ full_text: |
148
+ # Harbor lights
149
+
150
+ Wayfinding, shelter, and a recognisable place to arrive. This branch converges with accessibility and weather.
151
+
152
+ connections:
153
+ - full_text: |
154
+ # Keep it running
155
+
156
+ Maintenance is a recurring connection back to the crossing itself, not a dead-end checklist.
157
+
158
+ connections:
159
+ - node_id: ferry
160
+ - full_text: |
161
+ # Spare lamps
162
+
163
+ An ordinary replacement can keep a small failure from becoming a cancelled crossing.
164
+
165
+ - full_text: |
166
+ # Workshop rota
167
+
168
+ Leave enough time between shifts for repairs that need care rather than speed.
169
+
170
+ - full_text: |
171
+ # Winter overhaul
172
+
173
+ The work outside the busy season determines what can be promised during it.
174
+
175
+ - full_text: |
176
+ # A distant boatyard
177
+
178
+ Some repairs connect the little ferry to skills and suppliers far beyond its own harbor.
179
+
180
+ - id: archive
181
+ full_text: |
182
+ # Remember the route
183
+
184
+ Old tickets, passenger sketches, changing names for the same stretch of water.
185
+
186
+ connections:
187
+ - summary: null
188
+
189
+ connections:
190
+ - full_text: |
191
+ # A little ritual
192
+
193
+ Perhaps tea before departure. Perhaps something we have not thought of yet.
194
+
195
+ connections:
196
+ - full_text: |
197
+ # Tea before departure
198
+
199
+ A cup can mark the beginning of a journey without making a ceremony of it.
200
+
201
+ - full_text: |
202
+ # A passenger sketchbook
203
+
204
+ Let the route accumulate drawings as well as timetables.
205
+
206
+ - full_text: |
207
+ # Returning each year
208
+
209
+ The same crossing can become a quiet measure of what has changed.
210
+
211
+ - full_text: |
212
+ # A future tradition
213
+
214
+ Leave this idea far out on the map: it may only become clear after many crossings.
215
+ `,b=g(y);if(!b.ok)throw Error(b.error);var x=b.graph;function S(){let e=u({uri:``,graph:x,attachment:`draft:0`,source:y,draft:y}),[t,n]=(0,m.useState)(``),[r,i]=(0,m.useState)(``),o=e.current,[,c]=(0,m.useState)(0),p=(0,m.useRef)(new Map),h=(0,m.useRef)(new Map),b=(0,m.useRef)(new Map);p.current.set(o.uri,o);let S=async(e,t)=>{let n=h.current.get(e)??e,r=p.current.get(n);if(r)return r;if(!/^https?:/.test(n))throw Error(`Local maps are not attached in this standalone viewer.`);let i=await fetch(n,{signal:t});if(!i.ok)throw Error(`Map loading failed (${i.status}).`);let a=d(i.url).uri,o=p.current.get(a);if(o)return await i.body?.cancel(),t.throwIfAborted(),h.current.set(e,a),o;let s=await i.text(),c=g(s);if(!c.ok)throw Error(c.error);return t.throwIfAborted(),h.current.set(e,a),{uri:a,graph:c.graph,source:s,draft:s,attachment:crypto.randomUUID()}},C=()=>{let t=new URL(location.href);if(e.current.uri){let n=e.current,r=n.target?l(n.graph.nodes).get(n.target):void 0;t.searchParams.set(`map`,n.uri+(r?`#${encodeURIComponent(r)}`:``))}else t.searchParams.delete(`map`);return t.hash=``,t},w=(t=!1)=>{let n=t?history.state?.lensmapVisit:void 0,r=n&&b.current.has(n)?n:crypto.randomUUID();return b.current.set(r,{index:e.index,entry:e.current}),{lensmapVisit:r}},T=async(t,r,a=!1)=>{let o=e.index;if(await e.go(t,S,r,void 0)){n(``),i(``);let t=r!==void 0||e.index===o?`replaceState`:`pushState`;history[t](w(t===`replaceState`),``,a?location.href:C())}};return(0,m.useEffect)(()=>{history.replaceState(w(!0),``,location.href);let t=new URL(location.href).searchParams.get(`map`);if(t||location.hash){let e=location.hash?(t?t.split(`#`,1)[0]:``)+location.hash:t;e&&T(e,0)}let n=t=>{let n=t.state?.lensmapVisit,r=n?b.current.get(n):void 0;if(r){let t=e.entries[r.index]===r.entry?r.index:r.entry;T(r.entry.uri,t,!0)}};window.addEventListener(`popstate`,n);let r=()=>{T(location.hash||`#`)};return window.addEventListener(`hashchange`,r),()=>{e.cancel(),window.removeEventListener(`popstate`,n),window.removeEventListener(`hashchange`,r)}},[]),(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)(s,{navigation:e,openAnyway:()=>{e.fallback&&T(e.fallback)}}),(0,_.jsx)(f,{graph:o.graph,navigationControls:(0,_.jsxs)(`nav`,{className:`segmented`,"aria-label":`Map navigation`,children:[(0,_.jsx)(`button`,{type:`button`,disabled:e.index===0,onClick:()=>{let t=e.index-1;T(e.entries[t].uri,t)},children:`Back`}),(0,_.jsx)(`button`,{type:`button`,disabled:e.index>=e.entries.length-1,onClick:()=>{let t=e.index+1;T(e.entries[t].uri,t)},children:`Forward`})]}),thoughtLink:e=>a(location.href,o.uri,e),updateMode:`replace`,initialState:o.view,initialTarget:o.target,initialOverview:o.overview,onStateChange:e=>{o.view=e},onActivatePortal:e=>{if(e.destination){if(e.destination.kind===`map_link`)T(e.destination.url);else try{let t=d(e.destination.url,o.uri);if(!/^https?:/.test(t.uri))throw Error(`Standalone external opening supports HTTP(S) only.`);window.open(t.uri+(t.fragment?`#${encodeURIComponent(t.fragment)}`:``),`_blank`,`noopener`)}catch(e){n(e instanceof Error?e.message:`Opening failed.`)}}},renderSidebar:a=>(0,_.jsx)(v,{...a,source:o.draft??``,applied:o.source??``,error:t,status:r,onSourceChange:e=>{o.draft=e,c(e=>e+1)},onApply:()=>{e.cancel(),e.error=``;let t=g(o.draft??``);t.ok?(o.graph=t.graph,o.source=o.draft,o.attachment=crypto.randomUUID(),n(``),i(`Applied ${t.graph.nodes.length} thoughts and ${t.graph.edges.length} connections.`),c(e=>e+1)):(n(t.error),i(``))},onLoadSample:()=>{o.draft=y,c(e=>e+1),n(``),i(`Sample loaded into the draft. Apply YAML to change the map.`)}})},e.serial)]})}var C=document.getElementById(`root`);if(!C)throw Error(`Lensmap could not find its root element.`);(0,h.createRoot)(C).render((0,_.jsx)(m.StrictMode,{children:(0,_.jsx)(S,{})}));
@@ -0,0 +1 @@
1
+ import{c as e,h as t,l as n,m as r,n as i,o as a,r as o,t as s,u as c}from"./styles-CWsrvT4d.js";var l=t(),u=r();function d(e,t){let n=!1,r,i=new URLSearchParams(location.hash.slice(1)).get(`token`);i&&history.replaceState(null,``,location.pathname+location.search);let a=new AbortController;return(async()=>{if(i&&!(await fetch(`/api/auth`,{method:`POST`,credentials:`same-origin`,headers:{"Content-Type":`application/json`},body:JSON.stringify({token:i}),signal:a.signal})).ok)throw Error(`Preview authorization failed. Reopen the preview from Zed.`);n||(r=new EventSource(`/api/events`),r.onopen=()=>t(!0),r.onerror=()=>t(!1,`Disconnected or unauthorized. Reopen the preview from Zed after a server restart.`),r.onmessage=n=>{try{e(JSON.parse(n.data))}catch{t(!1,`The bridge sent an invalid update.`)}})})().catch(e=>{n||t(!1,e instanceof Error?e.message:`Preview connection failed.`)}),()=>{n=!0,a.abort(),r?.close()}}async function f(e,t){let n=await fetch(`/api/reveal`,{method:`POST`,credentials:`same-origin`,headers:{"Content-Type":`application/json`},body:JSON.stringify({uri:e,key:t})});if(!n.ok){let e=await n.json();throw Error(e.error??`Source reveal failed.`)}}async function p(e,t){let n=await fetch(`/api/open-destination`,{method:`POST`,credentials:`same-origin`,headers:{"Content-Type":`application/json`},body:JSON.stringify({uri:e.uri,key:t,baseRevision:e.revision,attachmentId:e.attachmentId})});if(!n.ok){let e=await n.json();throw Error(e.error??`Host destination opening failed.`)}}var m=a(),h=e=>{try{return decodeURIComponent(new URL(e).pathname.split(`/`).at(-1)??e)}catch{return e}};function g(){let[t,r]=(0,l.useState)([]),a=(0,l.useRef)(new Map),u=(0,l.useRef)(new Map),[g,_]=(0,l.useState)(null),v=o({uri:``,graph:{nodes:[],edges:[]},attachment:``},e=>{e.current.uri&&_(e.current.uri)}),[y,b]=(0,l.useState)(!1),[x,S]=(0,l.useState)(``),[C,w]=(0,l.useState)(``),[T,E]=(0,l.useState)(!1),[D,O]=(0,l.useState)(!1),k=(0,l.useRef)(void 0),[A,j]=(0,l.useState)(),M=(0,l.useRef)(0),N=(e,t)=>{_(e),j({key:t,sequence:++M.current})},P=(0,l.useEffectEvent)(e=>{if(e.type===`session`){let t=a.current;a.current=new Map(e.documents.map(e=>[e.uri,e]));for(let e of u.current.keys())a.current.has(e)||u.current.delete(e);k.current&&(!a.current.has(k.current.uri)||t.get(k.current.uri)?.attachmentId!==a.current.get(k.current.uri)?.attachmentId)&&(k.current=void 0),g&&(!a.current.has(g)||t.get(g)?.attachmentId!==a.current.get(g)?.attachmentId)&&j(void 0),r(e.documents),_(t=>t&&a.current.has(t)?t:e.activeUri??e.documents[0]?.uri??null)}else if(e.type===`snapshot`)a.current.get(e.snapshot.uri)?.attachmentId!==e.snapshot.attachmentId&&(k.current?.uri===e.snapshot.uri&&(k.current=void 0),g===e.snapshot.uri&&j(void 0)),a.current.set(e.snapshot.uri,e.snapshot),r([...a.current.values()]),_(t=>t??e.snapshot.uri);else if(e.type===`focus`)a.current.get(e.uri)?.graph?.nodes.some(t=>t.key===e.key)&&N(e.uri,e.key);else if(e.type===`caret`){let t=a.current.get(e.uri);if(!t||t.revision!==e.revision||!t.valid||t.pending){k.current=void 0;return}let n=k.current?.uri!==e.uri||k.current?.key!==e.key;k.current={uri:e.uri,key:e.key},T&&n&&e.key&&N(e.uri,e.key)}});(0,l.useEffect)(()=>d(P,(e,t)=>{b(e),S(t??``)}),[]);let F=t.find(e=>e.uri===g),I=(0,l.useMemo)(()=>{if(!F?.graph)return null;let e=u.current.get(F.uri);if(e?.attachmentId===F.attachmentId&&e?.revision===F.graphRevision&&JSON.stringify(e.semantic)===JSON.stringify(F.graph))return e.layout;let t=c(F.graph,e?.attachmentId===F.attachmentId?e:void 0);return u.current.set(F.uri,{attachmentId:F.attachmentId,revision:F.graphRevision,semantic:F.graph,layout:t}),t},[F]),L=async e=>{let t=a.current.get(e);if(!t)throw Error(`Destination map is not attached. Open it in the editor before navigating; the current map is retained.`);if(!t.valid||t.pending||!t.graph)throw Error(`Destination attached analysis is not currently valid.`);return{uri:e,graph:c(t.graph),attachment:t.attachmentId??``}};if(F&&I){if(v.current.uri!==F.uri)v.cancel(),v.entries=[...v.entries.slice(0,v.index+1),{uri:F.uri,graph:I,attachment:F.attachmentId??``}],v.index++;else{if(v.current.attachment!==(F.attachmentId??``)){let t=e(v.current,{uri:F.uri,graph:I,attachment:F.attachmentId??``},v.current.view);v.current.view&&!t&&(v.error=`Previous view target is removed or unavailable.`),v.current.view=t,v.current.target=void 0}v.current.graph=I,v.current.attachment=F.attachmentId??``}}let R=async(e,t)=>{await v.go(e,L,t)&&(_(v.current.uri),j(void 0))},z=(0,l.useRef)(!1);(0,l.useEffect)(()=>{if(z.current||!F?.valid||!I)return;z.current=!0;let e=new URL(location.href).searchParams.get(`map`),t=e?e.split(`#`,1)[0]+location.hash:location.hash;t&&R(t)},[F,I]);let B=y?F?F.pending?`Analyzing r${F.revision} · showing r${F.graphRevision??`—`}`:F.valid?`Live · r${F.revision}`:`Source r${F.revision} invalid · showing r${F.graphRevision??`—`} · stale`:`Waiting for a .lensmap document`:`Disconnected`;return(0,m.jsxs)(`div`,{className:`bridge-shell`,children:[(0,m.jsxs)(`header`,{className:`bridge-bar`,children:[(0,m.jsx)(`strong`,{children:`◎ Lensmap`}),(0,m.jsx)(`button`,{type:`button`,disabled:v.index<=1,onClick:()=>{let e=v.index-1;R(v.entries[e].uri,e)},children:`Back`}),(0,m.jsx)(`button`,{type:`button`,disabled:v.index>=v.entries.length-1,onClick:()=>{let e=v.index+1;R(v.entries[e].uri,e)},children:`Forward`}),(0,m.jsxs)(`select`,{"aria-label":`Document`,value:g??``,onChange:e=>{R(e.target.value),j(void 0),w(``)},children:[!t.length&&(0,m.jsx)(`option`,{value:``,children:`No attached document`}),t.map(e=>(0,m.jsx)(`option`,{value:e.uri,children:h(e.uri)},e.uri))]}),(0,m.jsxs)(`label`,{children:[(0,m.jsx)(`input`,{type:`checkbox`,checked:T,onChange:e=>{E(e.target.checked);let t=k.current;e.target.checked&&t?.key&&N(t.uri,t.key)}}),`Follow editor caret`]}),(0,m.jsx)(`button`,{type:`button`,"aria-pressed":D,onClick:()=>O(e=>!e),children:`Thought details`}),(0,m.jsx)(`span`,{role:`status`,"data-testid":`bridge-status`,children:B})]}),(0,m.jsx)(i,{navigation:v,openAnyway:()=>{v.fallback&&R(v.fallback)}}),(x||C)&&(0,m.jsxs)(`p`,{className:`bridge-notice`,children:[C||x,` The last map is retained.`]}),F&&!F.pending&&F.diagnostics.length>0&&(0,m.jsx)(`div`,{className:`bridge-errors`,role:`alert`,children:F.diagnostics.map(e=>(0,m.jsx)(`p`,{children:e.message},`${e.range.start.line}:${e.range.start.character}:${e.message}`))}),I&&g?(0,m.jsx)(s,{thoughtLink:e=>n(location.href,g,e),initialState:v.current.view,initialTarget:v.current.target,initialOverview:v.current.overview,onStateChange:e=>{v.current.view=e},onActivatePortal:e=>{if(!e.destination)return;if(e.destination.kind===`map_link`){R(e.destination.url);return}let t=a.current.get(g);t&&p(t,e.key).catch(e=>w(e instanceof Error?e.message:`Host opening failed.`))},graph:I,updateMode:`live`,focusRequest:A,showInspector:D,onRevealSource:e=>{w(``),f(g,e).catch(e=>w(e instanceof Error?e.message:`Source reveal failed.`))}},`${g}:${F?.attachmentId??``}:${v.serial}`):(0,m.jsxs)(`section`,{className:`bridge-empty`,children:[(0,m.jsx)(`h1`,{children:`Open a .lensmap document in Zed`}),(0,m.jsx)(`p`,{children:`Unsaved buffer contents drive this preview. Use the Lensmap code actions to open or centre it.`}),(0,m.jsx)(`p`,{children:`The format and integration are experimental. Source editing stays in your editor.`})]})]})}var _=document.getElementById(`root`);if(!_)throw Error(`Lensmap could not find its preview root.`);(0,u.createRoot)(_).render((0,m.jsx)(g,{}));
@@ -0,0 +1 @@
1
+ .bridge-shell{min-height:100dvh}.bridge-bar{background:#16212b;border-bottom:1px solid #35434f;flex-wrap:wrap;align-items:center;gap:12px;padding:12px 20px;display:flex}.bridge-bar strong{color:#a9d7d2;font-size:18px}.bridge-bar label{align-items:center;gap:6px;font-size:13px;display:flex}.bridge-bar select{color:inherit;background:#202c38;border:1px solid #374451;border-radius:6px;max-width:min(280px,90vw);padding:7px}.bridge-bar [role=status]{color:#abc4c8;margin-left:auto;font-size:12px}.bridge-shell>main{max-width:none;padding:14px 20px}.bridge-shell .masthead{display:none}.workspace.canvas-only{grid-template-columns:minmax(0,1fr)}.bridge-shell .map{height:clamp(440px,77vh,1100px)}.bridge-notice,.bridge-errors{color:#f7c46c;white-space:pre-wrap;background:#342c24;margin:0;padding:8px 20px;font-size:13px}.bridge-errors{max-height:160px;overflow:auto}.bridge-errors p{margin:0}.bridge-empty{max-width:760px;margin:auto;padding:48px 24px}@media (width<=700px){.bridge-bar{gap:8px;padding:10px}.bridge-bar [role=status]{flex-basis:100%;margin-left:0}.bridge-shell>main{padding:10px}}
@@ -0,0 +1 @@
1
+ :root{color:#e7eef4;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;font-synthesis:none;background:#10171f;font-family:Inter,ui-sans-serif,system-ui,sans-serif}*{box-sizing:border-box}body{min-width:320px;margin:0}button,input,textarea{font:inherit}button{color:inherit;cursor:pointer;background:#202c38;border:1px solid #374451;border-radius:8px;padding:8px 12px}button:hover{background:#2b3a49}button:disabled{opacity:.4;cursor:default}button:focus-visible,a:focus-visible,input:focus-visible,textarea:focus-visible,summary:focus-visible,.sidebar-resizer:focus-visible,.map:focus-visible{outline-offset:4px;outline:3px solid #f7c46c}main{max-width:1800px;margin:auto;padding:28px 36px 18px}.masthead{align-items:center;gap:24px;margin-bottom:26px;display:flex}.brand{align-items:center;gap:10px;display:flex}h1{letter-spacing:-1px;margin:0;font-size:25px}.brand-mark{color:#a9d7d2;font-size:34px}.tag,.phase,.note-eyebrow,.map-caption,.page-footer{letter-spacing:.07em;font-family:ui-monospace,monospace;font-size:11px}.tag{color:#abc4c8;background:#233238;border-radius:5px;margin-left:6px;padding:6px 8px}.masthead>p{color:#9aaab8;font-size:13px}.phase{color:#b4bec9;white-space:nowrap;margin-left:auto}.workspace{grid-template-columns:minmax(0, 1fr) 10px var(--sidebar-width,340px);background:#16212b;border:1px solid #35434f;border-radius:16px;display:grid;overflow:hidden}.map-column{min-width:0}.toolbar{background:#1b2732;border-bottom:1px solid #35434f;flex-wrap:wrap;align-items:center;gap:14px;padding:14px 18px;display:flex}.segmented{background:#101b25;border:none;border-radius:9px;gap:3px;min-width:0;margin:0;padding:3px;display:flex}.segmented button{color:#a7b7c6;background:0 0;border:none;font-size:12px}.segmented button[aria-pressed=true]{color:#edf8f7;background:#365054}.strength{color:#bdcbd6;align-items:center;gap:8px;font-size:12px;display:flex}.strength input{accent-color:#9ad6c9;width:88px}.strength output{font-variant-numeric:tabular-nums;min-width:28px}.quiet{background:0 0;margin-left:auto;font-size:12px}.map{isolation:isolate;cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none;background-color:#14202a;background-image:radial-gradient(#3b4a553d 1px,#0000 1px);background-size:24px 24px;height:clamp(490px,67vh,780px);position:relative;overflow:hidden}.map.dragging{cursor:grabbing}.map.overview{cursor:default}.graph-scene{transform-origin:50%;position:absolute;inset:0}.focus-ring{pointer-events:none;border:1px solid #99cfcf15;border-radius:50%;width:260px;height:260px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);box-shadow:0 0 100px 65px #9edbc704}.map-caption{color:#98adb9;font-size:10px}.connections{pointer-events:none;position:absolute;inset:0;overflow:visible}.edge{fill:none;stroke-width:2.5px;opacity:.92}.edge-hit{fill:none;stroke:#0000;stroke-width:16px;pointer-events:stroke}.destination-icon{font-size:1.4em;display:block}.portal-actions p{overflow-wrap:anywhere}.edge.context{opacity:.45}.neighbourhood-action{font-size:12px}.neighbourhood-label{color:#a9d7d2;font-size:11px}.edge.connected{stroke-width:3.5px;opacity:1;filter:drop-shadow(0 0 2px)drop-shadow(0 0 6px)}.edge.connection-preview{stroke-width:4px;opacity:1;filter:drop-shadow(0 0 2px #d5f4ed)drop-shadow(0 0 7px)}.connection-chooser{z-index:6;-webkit-user-select:text;user-select:text;background:#162832f5;border:1px solid #89bfb3;border-radius:10px;flex-direction:column;width:min(290px,100% - 24px);max-height:45%;padding:12px;font-size:11px;display:flex;position:absolute;top:12px;right:12px}.connection-chooser h2{margin:0 0 6px;font-size:13px}.connection-chooser p{margin:0 0 8px;line-height:1.4}.connection-chooser ol{min-height:0;margin:0 0 8px;padding:0;list-style:none;overflow:auto}.connection-chooser li+li{margin-top:5px}.connection-option{text-align:left;overflow-wrap:anywhere;width:100%;padding:7px 9px}.connection-chooser strong,.connection-chooser span{display:block}.connection-chooser span{color:#bdcbd6;margin-top:3px}.connection-option[aria-pressed=true]{background:#304c51;border-color:#d5f4ed}.connection-chooser .outside-view{color:#f7c46c}.connection-actions{flex-wrap:wrap;gap:6px;display:flex}.connection-actions button{padding:6px 8px;font-size:11px}.edge-halo{fill:none;stroke:#14202a;stroke-width:8px}.node{text-align:center;transform-origin:50%;background:#1d303d;border:1.8px solid;border-radius:50%;justify-content:center;align-items:center;width:86px;height:86px;padding:10px;font-size:12px;line-height:1.3;display:flex;position:absolute;box-shadow:0 5px 12px #0003}.node>span{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.node.connected,.node.selected{box-shadow:0 0 5px 1px var(--branch-colour), 0 0 18px color-mix(in srgb, var(--branch-colour) 80%, transparent), inset 0 0 8px color-mix(in srgb, var(--branch-colour) 30%, transparent);border-width:2.5px}.node:hover{z-index:3;background:#334754}.node.selected{filter:brightness(1.15);z-index:2;background:#2d4651}.node:focus-visible{z-index:4;outline-offset:5px;outline:3px solid #f7c46c}.node[data-keyboard-target=true]:after{content:"";pointer-events:none;border:2px dashed #d5f4ed;border-radius:50%;position:absolute;inset:-10px}.keyboard-target-label{color:#d5f4ed}.node.unresolved{border-style:dashed;font-size:26px}.map.dragging .node{cursor:grabbing}.zoom-controls{z-index:5;background:#18242ff5;border:1px solid #3b4a55;border-radius:10px;align-items:center;gap:6px;padding:4px;display:flex;position:absolute;bottom:18px;left:18px}.zoom-controls button{background:0 0;border:none;padding:3px 10px;font-size:20px}.zoom-controls output{text-align:center;min-width:42px;font-size:12px}.map-footer{color:#aebfca;border-top:1px solid #35434f;padding:13px 18px;font-size:11px}.map-footer p{margin:0 0 9px;line-height:1.6}.legend{color:#d4dfeb;gap:22px;display:flex}.notes{background:#1c2833;flex-direction:column;min-width:0;max-height:calc(clamp(490px,67vh,780px) + 200px);padding:24px;display:flex;overflow:auto}.sidebar-resizer{cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none;background:#23333f;border:none;width:10px;margin:0}.sidebar-resizer:hover,.sidebar-resizer:focus-visible{background:#659a9b}.sidebar-section{border-top:1px solid #3a4855;padding:14px 0}.sidebar-section>summary{color:#c3d5dc;cursor:pointer;font-size:12px;font-weight:600}.sidebar-section[open]>summary{margin-bottom:16px}.source-label{margin-bottom:8px;font-size:12px;display:block}.playground textarea{resize:vertical;color:#dce9ed;tab-size:2;background:#101b24;border:1px solid #536b78;border-radius:6px;width:100%;height:360px;min-height:220px;padding:12px;font:12px/1.65 ui-monospace,monospace;display:block}.editor-help,.editor-state{color:#b0c2cd;font-size:11px;line-height:1.7}.editor-state{overflow-wrap:anywhere}.editor-actions{flex-wrap:wrap;gap:8px;margin-top:12px;display:flex}.editor-actions button{font-size:12px}.source-error{color:#ffc2b5;white-space:pre-wrap;overflow-wrap:anywhere;background:#412c2b;border-radius:6px;padding:12px;font-size:11px}.empty-map{text-align:center;color:#b6cbd5;place-content:center;padding:32px;display:grid;position:absolute;inset:0}.empty-map h2{font-family:Georgia,serif;font-weight:400}.note-eyebrow{color:#a3c7c7;justify-content:space-between;margin-bottom:26px;display:flex}.note-content{color:#c9d5df;overflow-wrap:anywhere;font-size:14px;line-height:1.8}.note-content h2{color:#f0f4f6;margin:0 0 18px;font-family:Georgia,serif;font-size:30px;font-weight:400;line-height:1.25}.note-content ul{padding-left:20px}.note-content blockquote{border-left:2px solid #a9d7d2;margin-left:0;padding-left:16px;font-style:italic}a{color:#a3d7ff;text-underline-offset:3px}.outgoing{margin-top:0}.outgoing button{text-align:left;background:0 0;border:none;justify-content:space-between;width:100%;padding:9px 0;font-size:13px;display:flex}.outgoing button:hover{color:#aee2d7}.outgoing p{color:#a4b5c2;font-size:12px}.page-footer{color:#93a5b4;justify-content:space-between;gap:16px;padding-top:18px;font-size:10px;display:flex}.sr-only{clip-path:inset(50%);white-space:nowrap;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}@media (width<=1100px){main{padding:20px}.masthead>p{display:none}.toolbar{gap:9px}.quiet{margin-left:0}}@media (width<=760px){main{padding:14px}.masthead{flex-wrap:wrap;gap:8px;margin-bottom:14px}.phase{display:none}.tag{font-size:10px}.workspace{grid-template-columns:minmax(0,1fr)}.map{height:510px}.notes{border-top:1px solid #35434f;max-height:none}.sidebar-resizer{display:none}.note-eyebrow{margin-bottom:18px}.page-footer{flex-direction:column;gap:7px}.map-caption{font-size:9px}.toolbar{padding:12px}.strength{font-size:11px}.strength input{width:70px}}