nebulosity 1.0.0 → 1.1.0
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/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +11 -11
- package/.next/diagnostics/route-bundle-stats.json +10 -10
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +5 -5
- package/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/server/app/_not-found/page/next-font-manifest.json +2 -3
- package/.next/server/app/_not-found/page.js +3 -2
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +27 -20
- package/.next/server/app/_not-found.segments/_full.segment.rsc +27 -20
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +34 -26
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +29 -23
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +33 -24
- package/.next/server/app/index.segments/_full.segment.rsc +29 -23
- package/.next/server/app/index.segments/_tree.segment.rsc +4 -5
- package/.next/server/app/page/build-manifest.json +3 -3
- package/.next/server/app/page/next-font-manifest.json +2 -3
- package/.next/server/app/page.js +4 -4
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__06blyow8irfw3._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__0bmqko-d8fd5k._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__0cmmlmvta4i0g._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__0jgr1dt1j95ku._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__0m0n163465-zb._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__0p7epsgp2b5m3._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__1i5e5sqg45ens._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__1vjt0neva-819._.js +3 -3
- package/.next/server/chunks/_0uqus0ehnijt-._.js +1 -1
- package/.next/server/chunks/lib_background_start_ts_11u_1uu8b9yuy._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0_3l5tdzqy-td._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__10ockmlli-lun._.js +3 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__0dyligeswbb47._.js → [root-of-the-server]__16-gclvix9mjd._.js} +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1auodc3c0rppy._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1cf6qt9agg_9-._.js +3 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1i9r59yk4i69p._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1l3qp8u9yc_ig._.js +3 -0
- package/.next/server/chunks/ssr/_1p-0wsx9qfm68._.js +13 -0
- package/.next/server/chunks/ssr/app_map_18iu6j19jsqmu._.js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_1r3iqtbm45lqp._.js +3 -0
- package/.next/server/chunks/ssr/node_modules_next_dist_client_components_builtin_global-error_0q-w892nagajq.js +3 -0
- package/.next/server/middleware-build-manifest.js +11 -11
- package/.next/server/next-font-manifest.js +1 -1
- package/.next/server/next-font-manifest.json +4 -6
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/preview-props.json +3 -3
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/{1rljem7o1jtdo.js → 18kfexnrqek07.js} +1 -1
- package/.next/static/chunks/1hwbgd4nvvhg8.js +1 -0
- package/.next/static/chunks/25r4lkkf1shfo.css +3 -0
- package/.next/static/chunks/35ubvk2ivdjgs.js +1 -0
- package/.next/static/chunks/3ieqvmcg3hofj.js +31 -0
- package/.next/static/chunks/3w00e1w2125pp.js +11 -0
- package/.next/static/media/733da6bd8dac84c8-s.3hlisc-pwq13z.woff2 +0 -0
- package/.next/static/media/784318cea1dfcfa4-s.0-oo9_1x_xmvg.woff2 +0 -0
- package/.next/static/media/7e114457660b62ed-s.36fqqi66-3tjb.woff2 +0 -0
- package/.next/static/media/85e86a88f68a624a-s.357dd_6abeqge.woff2 +0 -0
- package/.next/static/media/9a321a2c78eff0b5-s.p.1bkzbscqrt8rl.woff2 +0 -0
- package/.next/static/media/9fecbd3e5ea9bccb-s.3_z45zcoc-xoz.woff2 +0 -0
- package/.next/static/media/c64053b510252f55-s.1kd-r_s9smv5m.woff2 +0 -0
- package/.next/static/media/c6a5c2fdc12da45c-s.p.17-aodiw50953.woff2 +0 -0
- package/.next/static/media/f263782c6a4bddee-s.1gyfqm5yhhzs7.woff2 +0 -0
- package/.next/trace +1 -1
- package/.next/trace-build +1 -1
- package/README.md +13 -4
- package/app/globals.css +91 -54
- package/app/layout.tsx +20 -11
- package/app/map/BackButton.tsx +5 -5
- package/app/map/BoxActions.tsx +20 -12
- package/app/map/Breadcrumb.tsx +132 -46
- package/app/map/DetailPanel.tsx +408 -297
- package/app/map/Explorer.tsx +268 -0
- package/app/map/Header.tsx +105 -0
- package/app/map/HelpDialog.tsx +37 -34
- package/app/map/Legend.tsx +19 -75
- package/app/map/LineCard.tsx +187 -0
- package/app/map/MapBox.tsx +73 -38
- package/app/map/MapCanvas.tsx +344 -115
- package/app/map/MapToolbar.tsx +377 -0
- package/app/map/MapView.tsx +217 -213
- package/app/map/RepoMenu.tsx +13 -12
- package/app/map/RoutedEdge.tsx +48 -16
- package/app/map/SearchPalette.tsx +6 -6
- package/app/map/SourceViewer.tsx +19 -17
- package/app/map/StatusBar.tsx +166 -0
- package/app/map/embed.ts +22 -2
- package/app/map/emphasis.ts +94 -8
- package/app/map/explorerRows.ts +85 -0
- package/app/map/flowNodes.ts +12 -7
- package/app/map/format.ts +2 -2
- package/app/map/history.ts +4 -3
- package/app/map/inspector.ts +113 -0
- package/app/map/labels.ts +66 -7
- package/app/map/narrow.ts +165 -85
- package/app/map/opening.ts +16 -0
- package/app/map/portals.ts +2 -2
- package/app/map/routes.ts +1060 -55
- package/app/map/runs.ts +108 -0
- package/app/map/shell.ts +91 -0
- package/app/map/shortcuts.ts +2 -1
- package/app/map/summaries.ts +41 -5
- package/app/map/text.ts +9 -1
- package/app/map/theme.ts +197 -0
- package/app/map/useDismiss.ts +20 -6
- package/app/map/useEditorBridge.ts +12 -1
- package/app/map/useThemeKind.ts +28 -0
- package/app/map/useWindowSize.ts +37 -0
- package/app/map/viewport.ts +134 -16
- package/app/map/weights.ts +80 -0
- package/app/page.tsx +3 -5
- package/lib/background/child.ts +2 -0
- package/lib/background/service.ts +26 -10
- package/lib/contracts.ts +25 -2
- package/lib/db/migrations/0011_plain_summaries_prompt_version.sql +1 -0
- package/lib/db/migrations/meta/0011_snapshot.json +1498 -0
- package/lib/db/migrations/meta/_journal.json +7 -0
- package/lib/db/schema.ts +2 -1
- package/lib/db/store/map.ts +6 -0
- package/lib/db/store/summaries.ts +14 -8
- package/lib/engine/describe.ts +2 -0
- package/lib/index/summarize/budget.ts +5 -2
- package/lib/index/summarize/claude.ts +19 -4
- package/lib/index/summarize/compare.ts +2 -2
- package/lib/index/summarize/plan.ts +73 -13
- package/lib/index/summarize/preview.ts +12 -9
- package/lib/index/summarize/prompts.ts +122 -25
- package/lib/index/summarize/run.ts +48 -18
- package/lib/index/summarize/snapshot.ts +7 -2
- package/lib/index/summarize/status.ts +3 -2
- package/lib/tools/text.ts +3 -2
- package/mcp/server.ts +1 -1
- package/package.json +4 -2
- package/scripts/commands/summarize-command.ts +27 -9
- package/.next/server/chunks/ssr/[root-of-the-server]__0ekw8p2ramybv._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0fwbsqt9e2tr-._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__17_rqmx62cz59._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1zfp6sltse4hf._.js +0 -3
- package/.next/server/chunks/ssr/_1d2woi6toj_0_._.js +0 -3
- package/.next/server/chunks/ssr/_1wfc1zz26g5ji._.js +0 -9
- package/.next/server/chunks/ssr/app_0gw6epmxyea3q._.js +0 -3
- package/.next/server/chunks/ssr/node_modules_@swc_helpers_cjs__interop_require_default_cjs_1ztp13a5szri_._.js +0 -3
- package/.next/server/chunks/ssr/node_modules_next_dist_1c5t_vsn6u8fr._.js +0 -3
- package/.next/static/chunks/02x9pxyl2d8om.css +0 -3
- package/.next/static/chunks/0p-0bv2dzer18.js +0 -7
- package/.next/static/chunks/10u7p2gj02qay.js +0 -1
- package/.next/static/chunks/31qp8jfkw0x2h.js +0 -31
- package/.next/static/media/123d516c9baeb953-s.1qc6dx89jd2qv.woff2 +0 -0
- package/.next/static/media/41ba59f553543ecf-s.3cb5lpr2l6xud.woff2 +0 -0
- package/.next/static/media/44ed10da57ebfb0e-s.2y65deuc30pmy.woff2 +0 -0
- package/.next/static/media/48eea41d8317494f-s.p.0wiir8udbzjvx.woff2 +0 -0
- package/.next/static/media/49b1d53681a91a99-s.1yd4_i3e35931.woff2 +0 -0
- package/.next/static/media/67bba87670b402d1-s.1u30mmyq6lnsd.woff2 +0 -0
- package/.next/static/media/6fca7e74603dd412-s.02z5rrbg5nt92.woff2 +0 -0
- package/.next/static/media/73263b2957b73100-s.2qt3tdwt-y9x_.woff2 +0 -0
- package/.next/static/media/7ac0b1cd3e695dd0-s.1i-sca43-qwng.woff2 +0 -0
- package/.next/static/media/916cd8a3bde3edf8-s.p.0oace-s_gkfks.woff2 +0 -0
- package/.next/static/media/a45aa1001bf9176f-s.2vemzm0itqzfy.woff2 +0 -0
- package/.next/static/media/a74d2542cc4c775f-s.29yke99gs9qpn.woff2 +0 -0
- package/.next/static/media/ab664c0e419cc841-s.3cvcemwuzoywn.woff2 +0 -0
- package/.next/static/media/c1b11b726b36a5e5-s.2rixgazldzomk.woff2 +0 -0
- package/.next/static/media/d6c1ff7e86974212-s.p.40sczeszzbjw1.woff2 +0 -0
- package/.next/static/media/faab88e4be272d29-s.0rgnxg2b64rzs.woff2 +0 -0
- package/app/map/ActivityBar.tsx +0 -81
- package/app/map/StatusPill.tsx +0 -93
- package/app/map/TopBar.tsx +0 -84
- /package/.next/static/{prDi039MhjlFqDOOwZ4aR → of6twfEZ3j95xtfQB_NSL}/_buildManifest.js +0 -0
- /package/.next/static/{prDi039MhjlFqDOOwZ4aR → of6twfEZ3j95xtfQB_NSL}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{prDi039MhjlFqDOOwZ4aR → of6twfEZ3j95xtfQB_NSL}/_ssgManifest.js +0 -0
package/app/map/MapView.tsx
CHANGED
|
@@ -2,45 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { ReactFlowProvider } from "@xyflow/react";
|
|
5
|
-
import { IconChevronLeft, IconChevronRight } from "@tabler/icons-react";
|
|
6
5
|
import type { BridgeFromMap, BridgeToMap, LevelResponse, LiveEvent } from "@/lib/contracts";
|
|
7
6
|
import { api, ApiError } from "./api";
|
|
8
|
-
import BackButton from "./BackButton";
|
|
9
7
|
import Breadcrumb from "./Breadcrumb";
|
|
10
8
|
import DetailPanel from "./DetailPanel";
|
|
11
9
|
import HelpDialog from "./HelpDialog";
|
|
12
|
-
import ActivityBar from "./ActivityBar";
|
|
13
10
|
import { AGENT_ACTIVE_MS, agentFocus } from "./activity";
|
|
14
|
-
import { isBusyLevel } from "./emphasis";
|
|
15
|
-
import { formatCount } from "./format";
|
|
11
|
+
import { isBusyLevel, selectionLines, sidesWithoutLines, weakLinesHidden, type LineDirection } from "./emphasis";
|
|
16
12
|
import MapCanvas from "./MapCanvas";
|
|
17
13
|
import { followStep, historyDepth, historyStep, mapUrl, placeFromSearch, type MapPlace } from "./history";
|
|
18
|
-
import {
|
|
14
|
+
import { bannerForm, CARD_CLEAR, type BannerForm } from "./narrow";
|
|
19
15
|
import { boxFor, parentOf } from "./paths";
|
|
20
16
|
import { clearStart, markStart, measureAfterPaint } from "./perf";
|
|
21
|
-
import {
|
|
17
|
+
import { selectionShown } from "./portals";
|
|
22
18
|
import { mapShortcut } from "./shortcuts";
|
|
23
19
|
import { nextPulses, type Pulse } from "./pulses";
|
|
24
20
|
import RepoMenu from "./RepoMenu";
|
|
25
21
|
import SearchPalette from "./SearchPalette";
|
|
26
|
-
import
|
|
27
|
-
import
|
|
28
|
-
import
|
|
22
|
+
import { applyTheme, themeInputOf } from "./theme";
|
|
23
|
+
import Explorer from "./Explorer";
|
|
24
|
+
import Header from "./Header";
|
|
25
|
+
import Legend from "./Legend";
|
|
26
|
+
import MapToolbar from "./MapToolbar";
|
|
27
|
+
import { BELOW_SHARE, EXPLORER_WIDTH, INSPECTOR_WIDTH, RAIL_WIDTH, shellLayout } from "./shell";
|
|
28
|
+
import StatusBar from "./StatusBar";
|
|
29
|
+
import { EMBEDDED, useEditorBridge, useEmbedded } from "./useEditorBridge";
|
|
29
30
|
import { useIndexStatus } from "./useIndexStatus";
|
|
30
31
|
import { useLive } from "./useLive";
|
|
31
32
|
import { useSize } from "./useSize";
|
|
33
|
+
import { useWindowSize } from "./useWindowSize";
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* Levels fetched for one repo and index version (ADR-036), by folder and page: going back up a level is instant until
|
|
35
37
|
* the index moves.
|
|
36
38
|
*/
|
|
37
39
|
type LevelCache = { repo: string; version: number; levels: ReadonlyMap<string, LevelResponse> };
|
|
38
|
-
/** Set once the help has opened by itself, so it does so only on a first visit. */
|
|
39
|
-
const HELP_SEEN = "nebulosity.helpSeen";
|
|
40
40
|
|
|
41
41
|
const cacheKey = (place: MapPlace): string => `${place.path}\0${place.page}`;
|
|
42
42
|
/** How long a failed jump's message stays up when nothing else moves the Map. */
|
|
43
43
|
const FOCUS_ERROR_MS = 6_000;
|
|
44
|
+
/** The inspector as a drawer over the canvas's right side: 320 px, leaving at least 56 px of canvas beside it. */
|
|
45
|
+
const DRAWER_WIDTH = "min(320px, calc(100% - 56px))";
|
|
44
46
|
|
|
45
47
|
/**
|
|
46
48
|
* A jump to a path (focus(), below), and how it was asked for. The editor's `follow` rewrites the history entry rather
|
|
@@ -96,7 +98,7 @@ function MapScreen(){
|
|
|
96
98
|
setDetailsSelection(selected);
|
|
97
99
|
setDetailsFor(null);
|
|
98
100
|
}
|
|
99
|
-
// Whether the history entry on screen has one of the Map's own steps behind it, for
|
|
101
|
+
// Whether the history entry on screen has one of the Map's own steps behind it, for the header's Back
|
|
100
102
|
// (BackButton, history.ts's historyDepth). It shows only once a level is drawn, in the browser, so reading the
|
|
101
103
|
// history here can't make the server's render and the browser's differ.
|
|
102
104
|
const [canGoBack, setCanGoBack] = useState(() => typeof window !== "undefined" && historyDepth(window.history.state) > 0);
|
|
@@ -118,14 +120,27 @@ function MapScreen(){
|
|
|
118
120
|
// a busy level draws only the hovered or selected box's lines, unless the user asks for all of them; the choice
|
|
119
121
|
// holds across levels until it's turned off (owner, 2026-10-12)
|
|
120
122
|
const [showAllLines, setShowAllLines] = useState(false);
|
|
123
|
+
// Strong links only, on until it's turned off, and like Show all lines it holds across levels (ADR-070)
|
|
124
|
+
const [strongOnly, setStrongOnly] = useState(true);
|
|
125
|
+
// Which side of the selected box's lines is highlighted, the other drawn gray (emphasis.ts). It belongs to the box it
|
|
126
|
+
// was picked for, so it's All again whenever the selection changes: another box, none, another folder or page, or
|
|
127
|
+
// Back (the owner, 2026-10-29: "the default is view all"). Reset during render, as detailsFor is above.
|
|
128
|
+
const [direction, setDirection] = useState<LineDirection>("all");
|
|
129
|
+
const [directionFor, setDirectionFor] = useState(selected);
|
|
130
|
+
if (directionFor !== selected){
|
|
131
|
+
setDirectionFor(selected);
|
|
132
|
+
setDirection("all");
|
|
133
|
+
}
|
|
121
134
|
const [pulses, setPulses] = useState<ReadonlyMap<string, Pulse>>(new Map());
|
|
122
135
|
// the Map area's width: whether the panel covers the canvas and the page pill's wording go by it (narrow.ts); the
|
|
123
136
|
// pill's width and the banner's height decide where the banner goes
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
137
|
+
// the window's size, which the layout goes by (shell.ts), and the error banner's, which picks its form (narrow.ts)
|
|
138
|
+
const windowSize = useWindowSize();
|
|
139
|
+
// inside VS Code's panel, as the page draws it (hydration-safe)
|
|
140
|
+
const embedded = useEmbedded();
|
|
141
|
+
const [mainRef] = useSize<HTMLElement>();
|
|
127
142
|
const [bandRef, bandSize] = useSize<HTMLDivElement>();
|
|
128
|
-
const [cardRef
|
|
143
|
+
const [cardRef] = useSize<HTMLDivElement>();
|
|
129
144
|
|
|
130
145
|
// the focus effect's failure handler reads where the Map is when the answer comes back, not when it was asked, and
|
|
131
146
|
// the editor's `drawn` reads it when it is posted
|
|
@@ -219,7 +234,7 @@ function MapScreen(){
|
|
|
219
234
|
// A link that names the same place another way (?path=src%2Fvs, ?page=1) is rewritten instead, so Back needs one
|
|
220
235
|
// press (history.ts). Inside VS Code's panel the address keeps `embed=vscode` (P8), and a move an editor's follow
|
|
221
236
|
// made rewrites the entry instead of pushing one, so switching tabs doesn't fill Back (followStep). Each entry carries
|
|
222
|
-
// how many of the Map's own steps lie behind it (historyDepth), for the Back
|
|
237
|
+
// how many of the Map's own steps lie behind it (historyDepth), for the header's Back.
|
|
223
238
|
useEffect(() => {
|
|
224
239
|
const step = followStep(historyStep(window.location.pathname + window.location.search, place, EMBEDDED), historyMode.current);
|
|
225
240
|
historyMode.current = "push";
|
|
@@ -284,13 +299,19 @@ function MapScreen(){
|
|
|
284
299
|
|
|
285
300
|
/**
|
|
286
301
|
* What the editor asks for (API_CONTRACTS §20): `follow` when its active file changed, a deliberate `show`, and the
|
|
287
|
-
* files to light. Only the Map's own clicks open files in the editor, so nothing here echoes back
|
|
302
|
+
* files to light, and its color theme. Only the Map's own clicks open files in the editor, so nothing here echoes back
|
|
303
|
+
* (V1.8 plan P10).
|
|
288
304
|
*/
|
|
289
305
|
const onEditorMessage = useCallback((message: BridgeToMap) => {
|
|
290
306
|
if (message.type === "nebulosity.follow") focus(message.path, { history: "replace", quiet: true });
|
|
291
307
|
else if (message.type === "nebulosity.show") focus(message.path, { quiet: true });
|
|
292
308
|
else if (message.type === "nebulosity.highlight") setEditorHighlights(message.paths);
|
|
293
309
|
else if (message.type === "nebulosity.clear") setEditorHighlights([]);
|
|
310
|
+
else if (message.type === "nebulosity.theme"){
|
|
311
|
+
// the editor's own grays (ADR-070): set on the page's root, over globals.css's Dark 2026
|
|
312
|
+
const theme = themeInputOf(message.kind, message.colors);
|
|
313
|
+
if (theme !== null) applyTheme(document.documentElement, theme);
|
|
314
|
+
}
|
|
294
315
|
}, [focus]);
|
|
295
316
|
|
|
296
317
|
/**
|
|
@@ -307,19 +328,23 @@ function MapScreen(){
|
|
|
307
328
|
// each `ready` it posts drawnNow(), for a hello that lands after the level was drawn.
|
|
308
329
|
const editor = useEditorBridge(onEditorMessage, drawnNow);
|
|
309
330
|
|
|
310
|
-
/**
|
|
331
|
+
/**
|
|
332
|
+
* A box clicked on the canvas or in the explorer. It only selects, inside VS Code's panel as in a browser tab, so you
|
|
333
|
+
* can look around the Map while the editor keeps its file (the owner, 2026-10-29, ADR-062 amended).
|
|
334
|
+
*/
|
|
311
335
|
const select = useCallback((target: string | null) => {
|
|
312
336
|
if (target !== null) markStart("map:select");
|
|
313
337
|
setSelected(target);
|
|
314
|
-
|
|
315
|
-
editor.post({ type: "nebulosity.open", path: target });
|
|
316
|
-
}
|
|
317
|
-
}, [editor, level]);
|
|
338
|
+
}, []);
|
|
318
339
|
|
|
319
|
-
/**
|
|
340
|
+
/**
|
|
341
|
+
* Inside VS Code's panel, the editor opens a file when it's asked to: Open in VS Code in the inspector or View source
|
|
342
|
+
* (at its line there), a double-click on the file's box, or the Open beside it in a narrow panel (API_CONTRACTS §20).
|
|
343
|
+
*/
|
|
320
344
|
const openInEditor = useCallback((path: string, line?: number) => {
|
|
321
345
|
editor?.post({ type: "nebulosity.open", path, ...(line === undefined ? {} : { line }) });
|
|
322
346
|
}, [editor]);
|
|
347
|
+
const onOpenFile = editor === null ? undefined : openInEditor;
|
|
323
348
|
|
|
324
349
|
|
|
325
350
|
/** A tool call from Claude Code (PIVOT_SPEC.md §8): pulse the boxes it touched, and run its UI action. */
|
|
@@ -355,6 +380,14 @@ function MapScreen(){
|
|
|
355
380
|
// tick.
|
|
356
381
|
const agentKey = level === null ? "" : [...agentFocus(level, live.recent, highlights, now, clearedAt, editorHighlights)].sort().join("\0");
|
|
357
382
|
const agent = useMemo(() => new Set(agentKey === "" ? [] : agentKey.split("\0")), [agentKey]);
|
|
383
|
+
// The sides of the selection with no line on the page can't be picked (they'd highlight nothing), and one picked
|
|
384
|
+
// before the index moved its lines away counts as All.
|
|
385
|
+
const selectedOnLevel = selectionShown(level, selected);
|
|
386
|
+
const emptySides = useMemo(() => (level === null ? [] : sidesWithoutLines(level.edges, selectedOnLevel)), [level, selectedOnLevel]);
|
|
387
|
+
const shownDirection = emptySides.includes(direction) ? "all" : direction;
|
|
388
|
+
// the boxes joined to the selection by a highlighted line, which the explorer lights as the canvas does
|
|
389
|
+
const linked = useMemo(() => new Set(level === null ? [] : selectionLines(level.edges, selectedOnLevel, shownDirection)
|
|
390
|
+
.flatMap((e) => [e.source, e.target])), [level, selectedOnLevel, shownDirection]);
|
|
358
391
|
|
|
359
392
|
const onSwitched = useCallback(() => {
|
|
360
393
|
setPlace({ path: "", page: 0 });
|
|
@@ -388,26 +421,13 @@ function MapScreen(){
|
|
|
388
421
|
if (drawn !== null) editor.post(drawn);
|
|
389
422
|
});
|
|
390
423
|
}, [selected, editor, drawnNow]);
|
|
391
|
-
/** The repo's name in the
|
|
424
|
+
/** The repo's name in the breadcrumb goes back to the top level, like a site's name goes home (three-task test). */
|
|
392
425
|
const goHome = useCallback(() => open(""), [open]);
|
|
393
426
|
const closeHelp = useCallback(() => setHelpOpen(false), []);
|
|
394
|
-
|
|
395
|
-
//
|
|
396
|
-
// storage (a private window) just doesn't get it unasked; the ? button still works.
|
|
397
|
-
useEffect(() => {
|
|
398
|
-
const timer = setTimeout(() => {
|
|
399
|
-
try{
|
|
400
|
-
if (window.localStorage.getItem(HELP_SEEN) !== null) return;
|
|
401
|
-
window.localStorage.setItem(HELP_SEEN, "1");
|
|
402
|
-
setHelpOpen(true);
|
|
403
|
-
} catch{
|
|
404
|
-
// no storage: nothing to remember the first visit by
|
|
405
|
-
}
|
|
406
|
-
}, 0);
|
|
407
|
-
return () => clearTimeout(timer);
|
|
408
|
-
}, []);
|
|
427
|
+
// The help no longer opens by itself on a first visit: the legend sentence under the canvas says what the marks mean,
|
|
428
|
+
// and the full help stays behind ? (the Workbench redesign, ADR-070).
|
|
409
429
|
const closePanel = useCallback(() => setSelected(null), []);
|
|
410
|
-
// over the canvas, the
|
|
430
|
+
// over the canvas, the inspector's × closes the details and leaves the box selected, with its actions (BoxActions)
|
|
411
431
|
const closeDetails = useCallback(() => setDetailsFor(null), []);
|
|
412
432
|
const showDetails = useCallback((path: string) => setDetailsFor(path), []);
|
|
413
433
|
|
|
@@ -421,14 +441,15 @@ function MapScreen(){
|
|
|
421
441
|
const hasSelection = shownSelected !== null;
|
|
422
442
|
// Claude's alone: the editor's go when What depends' pick closes
|
|
423
443
|
const hasHighlights = highlights.length > 0;
|
|
424
|
-
//
|
|
425
|
-
//
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
//
|
|
430
|
-
|
|
431
|
-
|
|
444
|
+
// The layout at this window's size (shell.ts): where the explorer and the inspector go, what the bars keep, and the
|
|
445
|
+
// least height before the page scrolls down. Where the inspector would cover the whole canvas, a click only selects
|
|
446
|
+
// and the box offers Open and Details beside it (BoxActions); everywhere else the inspector shows the selection.
|
|
447
|
+
const shell = shellLayout({ width: windowSize.width, height: windowSize.height, embedded, selected: shownSelected !== null });
|
|
448
|
+
const covers = shell.inspector === "cover";
|
|
449
|
+
// Anywhere but over the whole canvas the inspector is the selection's details, so they count as asked for: a window
|
|
450
|
+
// narrowed while they're open keeps them, now over the canvas, rather than swapping them for the box's actions.
|
|
451
|
+
if (!covers && shownSelected !== null && detailsFor !== shownSelected) setDetailsFor(shownSelected);
|
|
452
|
+
const detailsOverCanvas = covers && shownSelected !== null && detailsFor === shownSelected;
|
|
432
453
|
useEffect(() => {
|
|
433
454
|
const onKey = (event: KeyboardEvent): void => {
|
|
434
455
|
const shortcut = mapShortcut(event, {
|
|
@@ -446,7 +467,7 @@ function MapScreen(){
|
|
|
446
467
|
event.preventDefault();
|
|
447
468
|
setPaletteOpen(true);
|
|
448
469
|
} else if (shortcut.type === "help") setHelpOpen(true);
|
|
449
|
-
// over the canvas, Escape closes the details first, as the
|
|
470
|
+
// over the canvas, Escape closes the details first, as the inspector's × does, and the box stays selected
|
|
450
471
|
else if (shortcut.type === "deselect"){
|
|
451
472
|
if (detailsOverCanvas) setDetailsFor(null);
|
|
452
473
|
else setSelected(null);
|
|
@@ -458,107 +479,153 @@ function MapScreen(){
|
|
|
458
479
|
return () => window.removeEventListener("keydown", onKey);
|
|
459
480
|
}, [levelPath, open, page, pageCount, turnPage, hasSelection, hasHighlights, clearHighlights, detailsOverCanvas]);
|
|
460
481
|
|
|
482
|
+
// how many lines Strong links only hides right now, before a hover adds any back
|
|
483
|
+
const weakHidden = level !== null && strongOnly ? weakLinesHidden(level.edges, shownSelected, agent) : 0;
|
|
461
484
|
const jumpError = focusError !== null && focusError.place === cacheKey(place) && focusError.selected === selected ? focusError.error : null;
|
|
462
485
|
const banner = levelError ?? jumpError;
|
|
463
486
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
const
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
// panel it's the panel's last row instead (narrow.ts)
|
|
472
|
-
const pillWidth = pill ? pillSize?.width ?? null : null;
|
|
487
|
+
// the inspector shows the selection, or beside the canvas with nothing selected the folder on screen; over the whole
|
|
488
|
+
// canvas only once the box's Details asks for it
|
|
489
|
+
const inspectorPath = shownSelected ?? (shell.inspector === "beside" && level !== null ? level.path : null);
|
|
490
|
+
const inspectorOpen = level !== null && inspectorPath !== null && shell.inspector !== "none" && (!covers || detailsOverCanvas);
|
|
491
|
+
const covered = inspectorOpen && covers;
|
|
492
|
+
// the selected box's actions, while the inspector would cover the canvas and its details aren't asked for yet
|
|
493
|
+
const actionsFor = covers && !inspectorOpen ? shownSelected : null;
|
|
473
494
|
const bannerOnCanvas = banner !== null && !covered;
|
|
495
|
+
const drawerOpen = inspectorOpen && shell.inspector === "drawer";
|
|
474
496
|
const bannerAs = bannerForm(bandSize?.height ?? null);
|
|
475
|
-
const
|
|
497
|
+
const busy = level !== null && isBusyLevel(level.edges.length);
|
|
498
|
+
const quietLines = !showAllLines && busy;
|
|
499
|
+
const items = level === null ? 0 : level.nodes.filter((n) => n.kind !== "portal").length;
|
|
476
500
|
|
|
477
501
|
if (statusError?.code === "INDEX_EMPTY") return <EmptyState />;
|
|
478
502
|
|
|
503
|
+
const toolbar = level && (
|
|
504
|
+
<MapToolbar
|
|
505
|
+
level={level}
|
|
506
|
+
strongOnly={strongOnly}
|
|
507
|
+
onStrongOnly={setStrongOnly}
|
|
508
|
+
busy={busy}
|
|
509
|
+
showAllLines={showAllLines}
|
|
510
|
+
onShowAllLines={setShowAllLines}
|
|
511
|
+
onTurnPage={turnPage}
|
|
512
|
+
canvasWidth={shell.canvasWidth}
|
|
513
|
+
inHeader={shell.toolbarInHeader}
|
|
514
|
+
narrow={windowSize.width < 480}
|
|
515
|
+
helpInMenu={embedded || windowSize.width < 480}
|
|
516
|
+
onHelp={() => setHelpOpen(true)}
|
|
517
|
+
// the header hides its search icon under 360 px while the toolbar shares it (Header)
|
|
518
|
+
searchInMenu={shell.toolbarInHeader && windowSize.width < 360}
|
|
519
|
+
onSearch={() => setPaletteOpen(true)}
|
|
520
|
+
hasSelection={shownSelected !== null}
|
|
521
|
+
direction={shownDirection}
|
|
522
|
+
onDirection={setDirection}
|
|
523
|
+
emptySides={emptySides}
|
|
524
|
+
/>
|
|
525
|
+
);
|
|
526
|
+
const statusBar = (dots: boolean) => (
|
|
527
|
+
<StatusBar
|
|
528
|
+
status={status}
|
|
529
|
+
recent={live.recent}
|
|
530
|
+
connected={live.connected}
|
|
531
|
+
highlightCount={highlights.length}
|
|
532
|
+
onClear={clearHighlights}
|
|
533
|
+
items={items}
|
|
534
|
+
weakHidden={weakHidden}
|
|
535
|
+
embedded={embedded}
|
|
536
|
+
dots={dots}
|
|
537
|
+
/>
|
|
538
|
+
);
|
|
539
|
+
const inspector = inspectorOpen && inspectorPath !== null && level && shell.inspector !== "none" && (
|
|
540
|
+
<DetailPanel
|
|
541
|
+
key={inspectorPath}
|
|
542
|
+
path={inspectorPath}
|
|
543
|
+
level={level}
|
|
544
|
+
indexVersion={version ?? 0}
|
|
545
|
+
summariesOff={status?.stages.summaries.state === "off"}
|
|
546
|
+
onFocus={focus}
|
|
547
|
+
onOpen={open}
|
|
548
|
+
onTurnPage={turnPage}
|
|
549
|
+
onClose={covers ? closeDetails : closePanel}
|
|
550
|
+
onOpenInEditor={onOpenFile}
|
|
551
|
+
placement={shell.inspector}
|
|
552
|
+
onScreenFolder={shownSelected === null}
|
|
553
|
+
alert={covered ? banner?.message : undefined}
|
|
554
|
+
/>
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
// The page fills the window; below the layout's least height it keeps that height and scrolls down, never sideways
|
|
558
|
+
// (shell.ts). Header, then the explorer, the canvas's column and the inspector beside it, then the status bar.
|
|
479
559
|
return (
|
|
480
|
-
<div className="
|
|
481
|
-
<
|
|
482
|
-
|
|
560
|
+
<div data-testid="map-shell" style={{ minHeight: shell.minHeight }} className="flex h-dvh flex-col">
|
|
561
|
+
<Header
|
|
562
|
+
crumb={level && repo
|
|
563
|
+
? <Breadcrumb repo={<RepoMenu repo={repo} onSwitched={onSwitched} onHome={goHome} />} repoRoot={repo} trail={level.breadcrumb} onOpen={open} compact={windowSize.width < 360} />
|
|
564
|
+
: <span className="min-w-0 flex-1" />}
|
|
483
565
|
onSearch={() => setPaletteOpen(true)}
|
|
484
|
-
status={<StatusPill status={status} />}
|
|
485
566
|
onHelp={() => setHelpOpen(true)}
|
|
567
|
+
embedded={embedded}
|
|
568
|
+
canGoBack={canGoBack}
|
|
569
|
+
toolbar={shell.toolbarInHeader ? toolbar : undefined}
|
|
570
|
+
status={shell.status === "inHeader" ? statusBar(true) : undefined}
|
|
486
571
|
/>
|
|
487
|
-
<div className="
|
|
488
|
-
{level &&
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
{
|
|
503
|
-
{
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
{
|
|
513
|
-
|
|
514
|
-
|
|
572
|
+
<div className="flex min-h-0 flex-1">
|
|
573
|
+
{level && shell.explorer !== "none" && (
|
|
574
|
+
<div style={{ width: shell.explorer === "full" ? EXPLORER_WIDTH : RAIL_WIDTH }} className="relative z-10 shrink-0">
|
|
575
|
+
<Explorer level={level} selected={shownSelected} linked={linked} form={shell.explorer} onSelect={select} onOpen={open} />
|
|
576
|
+
</div>
|
|
577
|
+
)}
|
|
578
|
+
<div className="flex min-w-0 flex-1 flex-col">
|
|
579
|
+
{!shell.toolbarInHeader && toolbar}
|
|
580
|
+
<main ref={mainRef} className="relative min-h-0 flex-1 overflow-hidden">
|
|
581
|
+
{level && (
|
|
582
|
+
<MapCanvas
|
|
583
|
+
level={level}
|
|
584
|
+
selected={shownSelected}
|
|
585
|
+
agent={agent}
|
|
586
|
+
pulses={pulses}
|
|
587
|
+
onSelect={select}
|
|
588
|
+
onOpen={open}
|
|
589
|
+
onTurnPage={turnPage}
|
|
590
|
+
onDrawn={onDrawn}
|
|
591
|
+
quietLines={quietLines}
|
|
592
|
+
strongOnly={strongOnly}
|
|
593
|
+
direction={shownDirection}
|
|
594
|
+
covered={covered}
|
|
595
|
+
actionsFor={actionsFor}
|
|
596
|
+
onDetails={showDetails}
|
|
597
|
+
onOpenFile={onOpenFile}
|
|
598
|
+
/>
|
|
599
|
+
)}
|
|
600
|
+
{!level && !levelError && <p className="absolute inset-0 grid place-items-center px-4 text-center text-[13px] text-text-2">Loading the map…</p>}
|
|
601
|
+
{/* left of a drawer, which would cover the line's end: centred in what's left, and measured there, so
|
|
602
|
+
a line that needs more than two lines there becomes the card */}
|
|
603
|
+
{banner && bannerOnCanvas && (
|
|
604
|
+
<div style={{ right: drawerOpen ? DRAWER_WIDTH : 0 }} className="pointer-events-none absolute inset-y-0 left-0">
|
|
605
|
+
<ErrorBanner message={banner.message} form={bannerAs} bandRef={bandRef} cardRef={cardRef} />
|
|
606
|
+
</div>
|
|
607
|
+
)}
|
|
608
|
+
{/* over the canvas: a drawer on its right, or the whole of it. The line's hover card keeps clear of
|
|
609
|
+
it, as of the banner and a box's actions (LineCard) */}
|
|
610
|
+
{(shell.inspector === "drawer" || shell.inspector === "cover") && inspector && (
|
|
611
|
+
<div data-inspector-place={shell.inspector} data-keep-clear style={{ width: shell.inspector === "drawer" ? DRAWER_WIDTH : "100%" }} className="absolute inset-y-0 right-0 z-10">
|
|
612
|
+
{inspector}
|
|
613
|
+
</div>
|
|
614
|
+
)}
|
|
615
|
+
</main>
|
|
616
|
+
{level && shell.legend && (
|
|
617
|
+
<Legend doorways={level.nodes.some((n) => n.kind === "portal")} agent={agent.size > 0} quiet={quietLines} />
|
|
618
|
+
)}
|
|
619
|
+
{shell.inspector === "below" && inspector && (
|
|
620
|
+
<div data-inspector-place="below" style={{ height: `${BELOW_SHARE * 100}%` }} className="min-h-0 shrink-0">{inspector}</div>
|
|
515
621
|
)}
|
|
516
622
|
</div>
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
{level && (
|
|
520
|
-
<MapCanvas
|
|
521
|
-
level={level}
|
|
522
|
-
selected={shownSelected}
|
|
523
|
-
agent={agent}
|
|
524
|
-
pulses={pulses}
|
|
525
|
-
onSelect={select}
|
|
526
|
-
onOpen={open}
|
|
527
|
-
onTurnPage={turnPage}
|
|
528
|
-
onDrawn={onDrawn}
|
|
529
|
-
quietLines={!showAllLines && isBusyLevel(level.edges.length)}
|
|
530
|
-
panelWidth={panelWidth}
|
|
531
|
-
covered={covered}
|
|
532
|
-
bannerBottom={bannerBottom}
|
|
533
|
-
actionsFor={actionsFor}
|
|
534
|
-
onDetails={showDetails}
|
|
535
|
-
/>
|
|
536
|
-
)}
|
|
537
|
-
{level && pill && (
|
|
538
|
-
<PagePill sizeRef={pillRef} level={level} onTurn={turnPage} panelWidth={panelWidth} form={pillForm(mainWidth === null ? null : mainWidth - panelWidth)} />
|
|
539
|
-
)}
|
|
540
|
-
{!level && !levelError && <p className="absolute inset-0 grid place-items-center px-4 text-center text-[13px] text-text-2">Loading the map…</p>}
|
|
541
|
-
{banner && bannerOnCanvas && (
|
|
542
|
-
<ErrorBanner message={banner.message} form={bannerAs} panelWidth={panelWidth} pillWidth={pillWidth} bandRef={bandRef} cardRef={cardRef} />
|
|
623
|
+
{shell.inspector === "beside" && inspector && (
|
|
624
|
+
<div data-inspector-place="beside" style={{ width: INSPECTOR_WIDTH }} className="min-h-0 shrink-0">{inspector}</div>
|
|
543
625
|
)}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
path={shownSelected}
|
|
548
|
-
level={level}
|
|
549
|
-
indexVersion={version ?? 0}
|
|
550
|
-
onFocus={focus}
|
|
551
|
-
onOpen={open}
|
|
552
|
-
onTurnPage={turnPage}
|
|
553
|
-
onClose={panel.covers ? closeDetails : closePanel}
|
|
554
|
-
onOpenInEditor={editor === null ? undefined : openInEditor}
|
|
555
|
-
covers={panel.covers}
|
|
556
|
-
alert={covered ? banner?.message : undefined}
|
|
557
|
-
/>
|
|
558
|
-
)}
|
|
559
|
-
</main>
|
|
560
|
-
<ActivityBar recent={live.recent} connected={live.connected} highlightCount={highlights.length} onClear={clearHighlights} />
|
|
561
|
-
{helpOpen && <HelpDialog onClose={closeHelp} />}
|
|
626
|
+
</div>
|
|
627
|
+
{shell.status === "bar" && statusBar(false)}
|
|
628
|
+
{helpOpen && <HelpDialog onClose={closeHelp} embedded={embedded} />}
|
|
562
629
|
{paletteOpen && (
|
|
563
630
|
<SearchPalette
|
|
564
631
|
onPick={(hit) => {
|
|
@@ -572,99 +639,36 @@ function MapScreen(){
|
|
|
572
639
|
);
|
|
573
640
|
}
|
|
574
641
|
|
|
575
|
-
/**
|
|
576
|
-
* Which page of a big folder is on screen, in the canvas's top-right corner with a button either side: the one place to
|
|
577
|
-
* turn the page with the mouse (← and → turn it too). The three-task test found the top bar's note went unseen; a bar at
|
|
578
|
-
* the bottom of the canvas then read as loud and, in the boxes' colour, blended in (owner review). Big arrows at the
|
|
579
|
-
* canvas's sides went too, after a second critique: two ways to do one thing, and the right one sat on a box when the
|
|
580
|
-
* panel was open. Like the other overlays it's a solid card in the logo's violet with no outline, so it reads as neither
|
|
581
|
-
* a box nor a line, its buttons cyan; it moves left of the panel when the panel opens, by the panel's own width. On a
|
|
582
|
-
* narrow canvas it says less (narrow.ts's pillForm): "Page 2 of 5", then "2 / 5", with the whole note as its title.
|
|
583
|
-
*/
|
|
584
|
-
function PagePill({ sizeRef, level, onTurn, panelWidth, form }: {
|
|
585
|
-
/** Measures the pill, which the error banner keeps clear of. */
|
|
586
|
-
sizeRef: (element: HTMLDivElement | null) => void;
|
|
587
|
-
level: LevelResponse;
|
|
588
|
-
onTurn: (page: number) => void;
|
|
589
|
-
panelWidth: number;
|
|
590
|
-
form: PillForm;
|
|
591
|
-
}){
|
|
592
|
-
const step = (side: "left" | "right") => {
|
|
593
|
-
const next = side === "left" ? level.page - 1 : level.page + 1;
|
|
594
|
-
const Icon = side === "left" ? IconChevronLeft : IconChevronRight;
|
|
595
|
-
const off = next < 0 || next >= level.pageCount;
|
|
596
|
-
return (
|
|
597
|
-
<button
|
|
598
|
-
type="button"
|
|
599
|
-
data-testid={`page-${side === "left" ? "previous" : "next"}`}
|
|
600
|
-
disabled={off}
|
|
601
|
-
onClick={() => onTurn(next)}
|
|
602
|
-
aria-label={`${side === "left" ? "Previous" : "Next"} page`}
|
|
603
|
-
title={`${side === "left" ? "Previous" : "Next"} page (${side === "left" ? "←" : "→"})`}
|
|
604
|
-
className="grid h-9 w-9 place-items-center rounded-md text-control hover:bg-overlay-line disabled:opacity-35 disabled:hover:bg-transparent"
|
|
605
|
-
>
|
|
606
|
-
<Icon aria-hidden size={22} stroke={2} />
|
|
607
|
-
</button>
|
|
608
|
-
);
|
|
609
|
-
};
|
|
610
|
-
// "Page 2 of 5 · 44 of 182 folders and files": the page in the text color, the count quieter
|
|
611
|
-
const note = pageNote(level);
|
|
612
|
-
const [pagePart, ...rest] = note.split(" · ");
|
|
613
|
-
return (
|
|
614
|
-
<div
|
|
615
|
-
ref={sizeRef}
|
|
616
|
-
style={{ right: panelWidth + 16 }}
|
|
617
|
-
className="absolute top-4 z-[7] flex items-center gap-1.5 rounded-lg bg-overlay p-1"
|
|
618
|
-
>
|
|
619
|
-
{step("left")}
|
|
620
|
-
<span data-testid="page-note" title={form === "full" ? undefined : note} className="whitespace-nowrap px-1 text-[14px] text-text">
|
|
621
|
-
{form === "numbers" ? `${level.page + 1} / ${level.pageCount}` : pagePart}
|
|
622
|
-
{form === "full" && rest.length > 0 && <span className="text-overlay-text-2"> · {rest.join(" · ")}</span>}
|
|
623
|
-
</span>
|
|
624
|
-
{step("right")}
|
|
625
|
-
</div>
|
|
626
|
-
);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
642
|
/**
|
|
630
643
|
* The Map's error line over the canvas (a failed jump, a level that wouldn't load: VISUAL_REFRESH.md §10). It takes no
|
|
631
|
-
* clicks, so it never stands between the pointer and the boxes
|
|
632
|
-
* - **band** (its place at normal sizes): 24 px down
|
|
633
|
-
*
|
|
634
|
-
* side gives way first, so the line moves left, then wraps.
|
|
635
|
-
* - **card**, where the band would need more than its two lines: a solid card in the canvas's top-left corner that reads
|
|
636
|
-
* over the boxes, its right edge clear of the pill and of the zoom controls.
|
|
644
|
+
* clicks, so it never stands between the pointer and the boxes under it.
|
|
645
|
+
* - **band** (its place at normal sizes): 24 px down, centred over the canvas, wrapping as it must.
|
|
646
|
+
* - **card**, where the band would need more than its two lines: a solid card in the canvas's top-left corner.
|
|
637
647
|
* The band is always laid out, unseen while the card shows, so its height (which picks the form) is always known.
|
|
638
648
|
*/
|
|
639
|
-
function ErrorBanner({ message, form,
|
|
649
|
+
function ErrorBanner({ message, form, bandRef, cardRef }: {
|
|
640
650
|
message: string;
|
|
641
651
|
form: BannerForm;
|
|
642
|
-
panelWidth: number;
|
|
643
|
-
pillWidth: number | null;
|
|
644
652
|
bandRef: (element: HTMLDivElement | null) => void;
|
|
645
653
|
cardRef: (element: HTMLDivElement | null) => void;
|
|
646
654
|
}){
|
|
647
|
-
const clear = bandClear(pillWidth);
|
|
648
655
|
return (
|
|
649
656
|
<>
|
|
650
657
|
<div
|
|
651
658
|
ref={bandRef}
|
|
652
659
|
role={form === "band" ? "alert" : undefined}
|
|
653
660
|
aria-hidden={form === "band" ? undefined : true}
|
|
654
|
-
|
|
655
|
-
className={`pointer-events-none absolute left-0 top-6 flex justify-center px-4 text-[13px] text-danger ${form === "band" ? "" : "invisible"}`}
|
|
661
|
+
className={`pointer-events-none absolute inset-x-0 top-6 z-[7] flex justify-center px-4 text-[13px] text-danger ${form === "band" ? "" : "invisible"}`}
|
|
656
662
|
>
|
|
657
|
-
{
|
|
658
|
-
<span aria-hidden style={{ flex: `0 1 ${clear}px` }} />
|
|
659
|
-
<p style={{ maxWidth: `calc(100% - ${clear}px)` }} className="shrink-0 text-center [overflow-wrap:anywhere]">{message}</p>
|
|
660
|
-
<span aria-hidden style={{ width: clear }} className="shrink-0" />
|
|
663
|
+
<p data-keep-clear={form === "band" ? true : undefined} className="max-w-full rounded-[5px] bg-canvas px-2 text-center [overflow-wrap:anywhere]">{message}</p>
|
|
661
664
|
</div>
|
|
662
665
|
{form === "card" && (
|
|
663
666
|
<div
|
|
664
667
|
ref={cardRef}
|
|
665
668
|
role="alert"
|
|
666
|
-
|
|
667
|
-
|
|
669
|
+
data-keep-clear
|
|
670
|
+
style={{ maxWidth: `calc(100% - ${CARD_CLEAR}px)` }}
|
|
671
|
+
className="pointer-events-none absolute left-4 top-4 z-[7] rounded-md border border-divider bg-panel px-3 py-2 text-[13px] text-danger [overflow-wrap:anywhere]"
|
|
668
672
|
>
|
|
669
673
|
{message}
|
|
670
674
|
</div>
|
|
@@ -675,11 +679,11 @@ function ErrorBanner({ message, form, panelWidth, pillWidth, bandRef, cardRef }:
|
|
|
675
679
|
|
|
676
680
|
function EmptyState(){
|
|
677
681
|
return (
|
|
678
|
-
<div className="
|
|
679
|
-
<div className="max-w-md space-y-2 text-center text-[14px] text-text-2">
|
|
682
|
+
<div className="grid h-dvh place-items-center px-4">
|
|
683
|
+
<div className="max-w-md space-y-2 text-center text-[14px] text-text-2 [overflow-wrap:anywhere]">
|
|
680
684
|
<p className="text-[15px] font-medium text-text">No repository indexed yet.</p>
|
|
681
|
-
<p>Index one from a terminal
|
|
682
|
-
<p>In a checkout of Nebulosity
|
|
685
|
+
<p>Index one from a terminal with <code className="rounded-md bg-node px-1 font-mono text-[13px] text-text">nebulosity index <path-to-repo></code>, then reload.</p>
|
|
686
|
+
<p>In a checkout of Nebulosity it's <code className="rounded-md bg-node px-1 font-mono text-[13px] text-text">npm run index -- <path-to-repo></code>.</p>
|
|
683
687
|
</div>
|
|
684
688
|
</div>
|
|
685
689
|
);
|