@gmickel/gno 2.4.0 → 2.6.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/README.md +69 -6
- package/assets/skill/README.md +5 -1
- package/assets/skill/SKILL.md +98 -1
- package/assets/skill/cli-reference.md +140 -0
- package/assets/skill/examples.md +45 -0
- package/assets/skill/mcp-reference.md +62 -0
- package/assets/skill/recipes/capture-and-file.md +6 -0
- package/assets/skill/recipes/memory-file-decision.md +6 -0
- package/assets/skill/recipes/memory-supersede-fact.md +5 -0
- package/assets/skill/recipes/session-evidence-lookup.md +98 -0
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.4.0.zip → gno-browser-clipper-v2.6.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.6.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +2 -1
- package/spec/cli.md +358 -24
- package/spec/compiled-context.md +68 -0
- package/spec/mcp.md +223 -2
- package/spec/output-schemas/capture-receipt.schema.json +3 -0
- package/spec/output-schemas/compiled-context-check.schema.json +44 -0
- package/spec/output-schemas/compiled-context-file.schema.json +165 -0
- package/spec/output-schemas/compiled-context-preview.schema.json +142 -0
- package/spec/output-schemas/mcp-capture-result.schema.json +3 -0
- package/spec/output-schemas/memory-remember.schema.json +8 -2
- package/spec/output-schemas/request-status.schema.json +113 -0
- package/spec/output-schemas/sessions-automation-run.schema.json +46 -0
- package/spec/output-schemas/sessions-discovery.schema.json +38 -0
- package/spec/output-schemas/sessions-import-receipt.schema.json +156 -0
- package/spec/output-schemas/sessions-status.schema.json +432 -0
- package/src/app/compiled-context-files.ts +361 -0
- package/src/app/compiled-context.ts +240 -0
- package/src/app/context-surface.ts +7 -2
- package/src/cli/commands/ask.ts +14 -2
- package/src/cli/commands/audit.ts +4 -0
- package/src/cli/commands/capture.ts +55 -96
- package/src/cli/commands/context-compiled.ts +136 -0
- package/src/cli/commands/daemon.ts +41 -0
- package/src/cli/commands/ls.ts +3 -0
- package/src/cli/commands/memory.ts +12 -3
- package/src/cli/commands/request-status.ts +59 -0
- package/src/cli/commands/reset.ts +39 -5
- package/src/cli/commands/sessions.ts +713 -0
- package/src/cli/commands/shared.ts +14 -1
- package/src/cli/errors.ts +10 -3
- package/src/cli/program.ts +458 -1
- package/src/cli/session-binding.ts +49 -0
- package/src/config/types.ts +8 -0
- package/src/core/capture-publish.ts +239 -0
- package/src/core/capture-sync.ts +3 -0
- package/src/core/compiled-context.ts +254 -0
- package/src/core/context-budget.ts +2 -10
- package/src/core/file-lock.ts +22 -5
- package/src/core/folder-setup-planning.ts +2 -1
- package/src/core/memory-remember.ts +233 -122
- package/src/core/memory-types.ts +11 -0
- package/src/core/network-boundary-inventory.ts +16 -0
- package/src/core/request-receipts.ts +671 -0
- package/src/core/setup-receipt.ts +27 -20
- package/src/core/typed-metadata.ts +4 -0
- package/src/core/validation.ts +9 -2
- package/src/core/windows-private-path.ts +96 -0
- package/src/index.ts +11 -2
- package/src/ingestion/compiled-context.ts +15 -0
- package/src/ingestion/sync.ts +40 -8
- package/src/ingestion/walker.ts +5 -4
- package/src/llm/nodeLlamaCpp/simulator-install.ts +6 -2
- package/src/mcp/context.ts +8 -0
- package/src/mcp/http-egress.ts +15 -3
- package/src/mcp/http-transport.ts +2 -0
- package/src/mcp/retrieval-warnings.ts +24 -0
- package/src/mcp/tools/ask.ts +14 -1
- package/src/mcp/tools/capture.ts +87 -83
- package/src/mcp/tools/context.ts +46 -2
- package/src/mcp/tools/index.ts +119 -7
- package/src/mcp/tools/memory-remember.ts +7 -0
- package/src/mcp/tools/memory-shared.ts +7 -1
- package/src/mcp/tools/query.ts +3 -1
- package/src/mcp/tools/request-status.ts +73 -0
- package/src/mcp/tools/search.ts +3 -1
- package/src/mcp/tools/sessions.ts +208 -0
- package/src/mcp/tools/vsearch.ts +3 -1
- package/src/sdk/client.ts +252 -89
- package/src/sdk/index.ts +13 -0
- package/src/sdk/types.ts +82 -3
- package/src/serve/capture-service.ts +98 -32
- package/src/serve/compiled-context.ts +84 -0
- package/src/serve/config-sync.ts +3 -2
- package/src/serve/public/app.tsx +15 -1
- package/src/serve/public/components/CaptureModal.tsx +26 -8
- package/src/serve/public/components/sessions/AutomationPanel.tsx +800 -0
- package/src/serve/public/components/sessions/ImportReceipt.tsx +238 -0
- package/src/serve/public/components/sessions/SessionSearch.tsx +286 -0
- package/src/serve/public/components/sessions/SourcesPanel.tsx +541 -0
- package/src/serve/public/components/sessions/api.ts +40 -0
- package/src/serve/public/components/sessions/snippet.tsx +53 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/hooks/use-api.ts +10 -2
- package/src/serve/public/lib/request-intent.ts +69 -0
- package/src/serve/public/lib/workspace-actions.ts +12 -1
- package/src/serve/public/lib/workspace-tabs.ts +4 -0
- package/src/serve/public/pages/CompiledContext.tsx +364 -0
- package/src/serve/public/pages/Dashboard.tsx +17 -0
- package/src/serve/public/pages/DocView.tsx +15 -1
- package/src/serve/public/pages/DocumentEditor.tsx +139 -96
- package/src/serve/public/pages/Sessions.tsx +350 -0
- package/src/serve/resident-runtime.ts +43 -3
- package/src/serve/routes/api.ts +476 -147
- package/src/serve/routes/sessions.ts +766 -0
- package/src/serve/security.ts +9 -0
- package/src/serve/server.ts +248 -3
- package/src/serve/session-automation.ts +146 -0
- package/src/serve/spa-production-build.ts +6 -5
- package/src/sessions/archive.ts +348 -0
- package/src/sessions/automation-state.ts +444 -0
- package/src/sessions/automation-status.ts +239 -0
- package/src/sessions/automation.ts +1169 -0
- package/src/sessions/binding.ts +105 -0
- package/src/sessions/claude-hook.ts +240 -0
- package/src/sessions/config.ts +176 -0
- package/src/sessions/format.ts +191 -0
- package/src/sessions/import-child-env.ts +8 -0
- package/src/sessions/import-child.ts +152 -0
- package/src/sessions/parsers/claude-code.ts +259 -0
- package/src/sessions/parsers/codex.ts +303 -0
- package/src/sessions/parsers/hermes.ts +248 -0
- package/src/sessions/parsers/openclaw.ts +496 -0
- package/src/sessions/parsers/shared.ts +184 -0
- package/src/sessions/sanitize.ts +222 -0
- package/src/sessions/service.ts +1533 -0
- package/src/sessions/setup.ts +477 -0
- package/src/sessions/sources.ts +518 -0
- package/src/sessions/state.ts +118 -0
- package/src/sessions/types.ts +457 -0
- package/src/store/sqlite/adapter.ts +68 -16
- package/src/store/sqlite/scoped-index.ts +9 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.4.0.zip.sha256 +0 -1
|
@@ -73,6 +73,13 @@ import {
|
|
|
73
73
|
loadLatestLocalHistory,
|
|
74
74
|
type LocalHistoryEntry,
|
|
75
75
|
} from "../lib/local-history";
|
|
76
|
+
import {
|
|
77
|
+
clearRequestIntent,
|
|
78
|
+
type RequestIntent,
|
|
79
|
+
requestIdForIntent,
|
|
80
|
+
} from "../lib/request-intent";
|
|
81
|
+
|
|
82
|
+
const SAVE_INTENT_KEY = "gno:save-intent";
|
|
76
83
|
import { getActiveWikiLinkQuery } from "../lib/wiki-link";
|
|
77
84
|
|
|
78
85
|
interface PageProps {
|
|
@@ -123,6 +130,8 @@ interface UpdateDocResponse {
|
|
|
123
130
|
sourceHash: string;
|
|
124
131
|
modifiedAt?: string;
|
|
125
132
|
};
|
|
133
|
+
/** Present when the save carried a request ID. */
|
|
134
|
+
request?: { replayed: boolean };
|
|
126
135
|
}
|
|
127
136
|
|
|
128
137
|
interface DocsAutocompleteResponse {
|
|
@@ -132,7 +141,7 @@ interface DocsAutocompleteResponse {
|
|
|
132
141
|
type SaveStatus = "saved" | "saving" | "unsaved" | "error";
|
|
133
142
|
|
|
134
143
|
function useDebouncedCallback<T extends unknown[]>(
|
|
135
|
-
callback: (...args: T) =>
|
|
144
|
+
callback: (...args: T) => unknown,
|
|
136
145
|
delay: number
|
|
137
146
|
) {
|
|
138
147
|
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
@@ -309,23 +318,44 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
309
318
|
}
|
|
310
319
|
}, [syncScroll]);
|
|
311
320
|
|
|
312
|
-
//
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
321
|
+
// Autosave timers and Cmd+S run later than the render that scheduled them:
|
|
322
|
+
// read the current revision and last committed content from refs.
|
|
323
|
+
const docRef = useRef(doc);
|
|
324
|
+
const committedContentRef = useRef(originalContent);
|
|
325
|
+
useEffect(() => {
|
|
326
|
+
docRef.current = doc;
|
|
327
|
+
}, [doc]);
|
|
328
|
+
// One request ID per (document revision, content) save intent: retrying a
|
|
329
|
+
// save whose response was lost replays it instead of reporting a conflict.
|
|
330
|
+
const saveIntentRef = useRef<RequestIntent | null>(null);
|
|
331
|
+
|
|
332
|
+
/** Save `contentToSave`; resolves true once it is committed on disk. */
|
|
333
|
+
const persistContent = useCallback(
|
|
334
|
+
async (contentToSave: string): Promise<boolean> => {
|
|
335
|
+
const current = docRef.current;
|
|
336
|
+
if (!current) return false;
|
|
337
|
+
if (contentToSave === committedContentRef.current) {
|
|
338
|
+
setSaveStatus("saved");
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
316
341
|
|
|
317
342
|
setSaveStatus("saving");
|
|
318
343
|
setSaveError(null);
|
|
319
344
|
|
|
320
345
|
const { data, error: err } = await apiFetch<UpdateDocResponse>(
|
|
321
|
-
`/api/docs/${encodeURIComponent(
|
|
346
|
+
`/api/docs/${encodeURIComponent(current.docid)}`,
|
|
322
347
|
{
|
|
323
348
|
method: "PUT",
|
|
324
349
|
body: JSON.stringify({
|
|
325
350
|
content: contentToSave,
|
|
326
|
-
expectedSourceHash:
|
|
327
|
-
expectedModifiedAt:
|
|
328
|
-
uri:
|
|
351
|
+
expectedSourceHash: current.source.sourceHash,
|
|
352
|
+
expectedModifiedAt: current.source.modifiedAt,
|
|
353
|
+
uri: current.uri,
|
|
354
|
+
requestId: requestIdForIntent(
|
|
355
|
+
saveIntentRef,
|
|
356
|
+
`${current.uri}\u0000${current.source.sourceHash}\u0000${contentToSave}`,
|
|
357
|
+
SAVE_INTENT_KEY
|
|
358
|
+
),
|
|
329
359
|
}),
|
|
330
360
|
}
|
|
331
361
|
);
|
|
@@ -333,32 +363,49 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
333
363
|
if (err) {
|
|
334
364
|
setSaveStatus("error");
|
|
335
365
|
setSaveError(err);
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (data?.request?.replayed) {
|
|
370
|
+
// A replay reports an earlier commit; disk may have moved on since.
|
|
371
|
+
// Clear the change notice only if the file still holds that commit.
|
|
372
|
+
const { data: latest } = await apiFetch<DocData>(
|
|
373
|
+
`/api/doc?uri=${encodeURIComponent(current.uri)}`
|
|
374
|
+
);
|
|
375
|
+
if (latest?.source.sourceHash === data.version.sourceHash) {
|
|
376
|
+
setExternalChangeNotice(null);
|
|
377
|
+
}
|
|
336
378
|
} else {
|
|
379
|
+
// Written now against the loaded revision: the next change event is
|
|
380
|
+
// this save's own sync, and any earlier notice is stale.
|
|
337
381
|
ignoreDocEventsUntilRef.current = Date.now() + 5_000;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
382
|
+
setExternalChangeNotice(null);
|
|
383
|
+
}
|
|
384
|
+
const previous = committedContentRef.current;
|
|
385
|
+
if (previous !== contentToSave) {
|
|
386
|
+
appendLocalHistory(current.docid, previous);
|
|
387
|
+
refreshHistoryEntries(current.docid);
|
|
388
|
+
}
|
|
389
|
+
clearRequestIntent(saveIntentRef, SAVE_INTENT_KEY);
|
|
390
|
+
committedContentRef.current = contentToSave;
|
|
391
|
+
setSaveStatus("saved");
|
|
392
|
+
setOriginalContent(contentToSave);
|
|
393
|
+
setLastSaved(new Date());
|
|
394
|
+
if (data) {
|
|
395
|
+
const next = {
|
|
396
|
+
...current,
|
|
397
|
+
source: {
|
|
398
|
+
...current.source,
|
|
399
|
+
sourceHash: data.version.sourceHash,
|
|
400
|
+
modifiedAt: data.version.modifiedAt,
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
docRef.current = next;
|
|
404
|
+
setDoc(next);
|
|
359
405
|
}
|
|
406
|
+
return true;
|
|
360
407
|
},
|
|
361
|
-
[
|
|
408
|
+
[]
|
|
362
409
|
);
|
|
363
410
|
|
|
364
411
|
const handleCreateEditableCopy = useCallback(async () => {
|
|
@@ -476,16 +523,18 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
476
523
|
);
|
|
477
524
|
|
|
478
525
|
// Debounced auto-save
|
|
479
|
-
const { debouncedFn: debouncedSave } =
|
|
480
|
-
|
|
481
|
-
2000
|
|
482
|
-
);
|
|
526
|
+
const { debouncedFn: debouncedSave, cancel: cancelAutosave } =
|
|
527
|
+
useDebouncedCallback(persistContent, 2000);
|
|
483
528
|
|
|
484
529
|
// Handle content changes
|
|
485
530
|
const handleContentChange = useCallback(
|
|
486
531
|
(newContent: string) => {
|
|
487
532
|
setContent(newContent);
|
|
488
|
-
if (newContent
|
|
533
|
+
if (newContent === originalContent) {
|
|
534
|
+
// Back to the committed text: drop a pending autosave of a draft.
|
|
535
|
+
cancelAutosave();
|
|
536
|
+
setSaveStatus("saved");
|
|
537
|
+
} else {
|
|
489
538
|
setSaveStatus("unsaved");
|
|
490
539
|
debouncedSave(newContent);
|
|
491
540
|
}
|
|
@@ -509,7 +558,7 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
509
558
|
setWikiLinkOpen(true);
|
|
510
559
|
setWikiLinkActiveIndex(0);
|
|
511
560
|
},
|
|
512
|
-
[originalContent, debouncedSave]
|
|
561
|
+
[cancelAutosave, originalContent, debouncedSave]
|
|
513
562
|
);
|
|
514
563
|
|
|
515
564
|
useEffect(() => {
|
|
@@ -530,55 +579,15 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
530
579
|
});
|
|
531
580
|
}, [doc?.collection, wikiLinkOpen, wikiLinkQuery]);
|
|
532
581
|
|
|
533
|
-
// Force save (Cmd+S) -
|
|
582
|
+
// Force save (Cmd+S) - supersedes a pending autosave, then triggers embedding
|
|
534
583
|
const handleForceSave = useCallback(async () => {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
// Save document
|
|
541
|
-
const { data, error: err } = await apiFetch<UpdateDocResponse>(
|
|
542
|
-
`/api/docs/${encodeURIComponent(doc.docid)}`,
|
|
543
|
-
{
|
|
544
|
-
method: "PUT",
|
|
545
|
-
body: JSON.stringify({
|
|
546
|
-
content,
|
|
547
|
-
expectedSourceHash: doc.source.sourceHash,
|
|
548
|
-
expectedModifiedAt: doc.source.modifiedAt,
|
|
549
|
-
uri: doc.uri,
|
|
550
|
-
}),
|
|
551
|
-
}
|
|
552
|
-
);
|
|
553
|
-
|
|
554
|
-
if (err) {
|
|
555
|
-
setSaveStatus("error");
|
|
556
|
-
setSaveError(err);
|
|
557
|
-
return;
|
|
584
|
+
cancelAutosave();
|
|
585
|
+
if (!hasUnsavedChanges) return;
|
|
586
|
+
if (await persistContent(content)) {
|
|
587
|
+
// Fire and forget - don't block on result
|
|
588
|
+
void apiFetch("/api/embed", { method: "POST" });
|
|
558
589
|
}
|
|
559
|
-
|
|
560
|
-
ignoreDocEventsUntilRef.current = Date.now() + 5_000;
|
|
561
|
-
if (originalContent !== content) {
|
|
562
|
-
appendLocalHistory(doc.docid, originalContent);
|
|
563
|
-
refreshHistoryEntries(doc.docid);
|
|
564
|
-
}
|
|
565
|
-
setSaveStatus("saved");
|
|
566
|
-
setOriginalContent(content);
|
|
567
|
-
setLastSaved(new Date());
|
|
568
|
-
if (data) {
|
|
569
|
-
setDoc({
|
|
570
|
-
...doc,
|
|
571
|
-
source: {
|
|
572
|
-
...doc.source,
|
|
573
|
-
sourceHash: data.version.sourceHash,
|
|
574
|
-
modifiedAt: data.version.modifiedAt,
|
|
575
|
-
},
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// Trigger embedding (fire and forget - don't block on result)
|
|
580
|
-
void apiFetch("/api/embed", { method: "POST" });
|
|
581
|
-
}, [hasUnsavedChanges, doc, content]);
|
|
590
|
+
}, [cancelAutosave, content, hasUnsavedChanges, persistContent]);
|
|
582
591
|
|
|
583
592
|
const loadDocument = useCallback(() => {
|
|
584
593
|
const uri = currentTarget.uri;
|
|
@@ -599,6 +608,7 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
599
608
|
const docContent = data.content ?? "";
|
|
600
609
|
setContent(docContent);
|
|
601
610
|
setOriginalContent(docContent);
|
|
611
|
+
committedContentRef.current = docContent;
|
|
602
612
|
refreshHistoryEntries(data.docid);
|
|
603
613
|
// Ensure CodeMirror reflects content after async load
|
|
604
614
|
requestAnimationFrame(() => {
|
|
@@ -617,10 +627,16 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
617
627
|
loadDocument();
|
|
618
628
|
}, [loadDocument]);
|
|
619
629
|
|
|
630
|
+
// Judge each change event once; later doc updates must not re-raise it.
|
|
631
|
+
const handledDocEventRef = useRef<string | null>(null);
|
|
620
632
|
useEffect(() => {
|
|
621
633
|
if (!doc || latestDocEvent?.uri !== doc.uri) {
|
|
622
634
|
return;
|
|
623
635
|
}
|
|
636
|
+
if (handledDocEventRef.current === latestDocEvent.changedAt) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
handledDocEventRef.current = latestDocEvent.changedAt;
|
|
624
640
|
if (Date.now() < ignoreDocEventsUntilRef.current) {
|
|
625
641
|
return;
|
|
626
642
|
}
|
|
@@ -752,7 +768,12 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
752
768
|
|
|
753
769
|
// Save and navigate
|
|
754
770
|
const handleSaveAndNavigate = async () => {
|
|
755
|
-
|
|
771
|
+
cancelAutosave();
|
|
772
|
+
// A failed save keeps the editor (and its error) instead of losing the draft.
|
|
773
|
+
if (!(await persistContent(content))) {
|
|
774
|
+
setShowUnsavedDialog(false);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
756
777
|
// Trigger embedding (fire and forget)
|
|
757
778
|
void apiFetch("/api/embed", { method: "POST" });
|
|
758
779
|
setShowUnsavedDialog(false);
|
|
@@ -793,9 +814,13 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
793
814
|
<TooltipProvider>
|
|
794
815
|
<Tooltip>
|
|
795
816
|
<TooltipTrigger asChild>
|
|
796
|
-
<div
|
|
797
|
-
|
|
798
|
-
|
|
817
|
+
<div
|
|
818
|
+
aria-label={text}
|
|
819
|
+
className="flex min-w-0 max-w-[16rem] shrink items-center gap-1.5 text-sm"
|
|
820
|
+
role="status"
|
|
821
|
+
>
|
|
822
|
+
<Icon className={`size-4 shrink-0 ${className}`} />
|
|
823
|
+
<span className="hidden truncate text-muted-foreground sm:inline">
|
|
799
824
|
{saveStatus === "saved" && lastSaved
|
|
800
825
|
? formatTime(lastSaved)
|
|
801
826
|
: text}
|
|
@@ -907,7 +932,7 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
907
932
|
<div className="flex h-screen flex-col overflow-hidden">
|
|
908
933
|
{/* Toolbar */}
|
|
909
934
|
<header className="glass shrink-0 border-border/50 border-b">
|
|
910
|
-
<div className="flex items-center gap-
|
|
935
|
+
<div className="flex items-center gap-2 px-2 py-2 sm:gap-3 sm:px-4">
|
|
911
936
|
{/* Home button - Scholarly Dusk brass accent */}
|
|
912
937
|
<Button
|
|
913
938
|
aria-label="Go to dashboard"
|
|
@@ -954,7 +979,7 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
954
979
|
<Separator className="h-5" orientation="vertical" />
|
|
955
980
|
|
|
956
981
|
{/* Document info */}
|
|
957
|
-
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
982
|
+
<div className="flex min-w-0 flex-1 items-center gap-2 overflow-hidden">
|
|
958
983
|
<PenIcon className="size-4 shrink-0 text-muted-foreground" />
|
|
959
984
|
<h1 className="truncate font-medium">{doc.title || doc.relPath}</h1>
|
|
960
985
|
{currentTarget.lineStart && (
|
|
@@ -968,7 +993,7 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
968
993
|
)}
|
|
969
994
|
{hasUnsavedChanges && (
|
|
970
995
|
<Badge
|
|
971
|
-
className="shrink-0 bg-yellow-500/20 text-yellow-500"
|
|
996
|
+
className="hidden shrink-0 bg-yellow-500/20 text-yellow-500 sm:inline-flex"
|
|
972
997
|
variant="outline"
|
|
973
998
|
>
|
|
974
999
|
Unsaved
|
|
@@ -982,6 +1007,7 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
982
1007
|
<Separator className="h-5" orientation="vertical" />
|
|
983
1008
|
|
|
984
1009
|
<Button
|
|
1010
|
+
className="hidden sm:inline-flex"
|
|
985
1011
|
onClick={() => {
|
|
986
1012
|
void navigator.clipboard.writeText(
|
|
987
1013
|
`${window.location.origin}${buildEditDeepLink({
|
|
@@ -999,7 +1025,7 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
999
1025
|
|
|
1000
1026
|
{/* Preview toggle */}
|
|
1001
1027
|
<Select onValueChange={setInsertPresetId} value={insertPresetId}>
|
|
1002
|
-
<SelectTrigger className="w-[180px]">
|
|
1028
|
+
<SelectTrigger className="hidden w-[180px] md:flex">
|
|
1003
1029
|
<SelectValue placeholder="Preset" />
|
|
1004
1030
|
</SelectTrigger>
|
|
1005
1031
|
<SelectContent>
|
|
@@ -1012,7 +1038,12 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
1012
1038
|
)}
|
|
1013
1039
|
</SelectContent>
|
|
1014
1040
|
</Select>
|
|
1015
|
-
<Button
|
|
1041
|
+
<Button
|
|
1042
|
+
className="hidden md:inline-flex"
|
|
1043
|
+
onClick={handleInsertPreset}
|
|
1044
|
+
size="sm"
|
|
1045
|
+
variant="outline"
|
|
1046
|
+
>
|
|
1016
1047
|
Insert preset
|
|
1017
1048
|
</Button>
|
|
1018
1049
|
|
|
@@ -1047,7 +1078,7 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
1047
1078
|
aria-label={
|
|
1048
1079
|
syncScroll ? "Disable scroll sync" : "Enable scroll sync"
|
|
1049
1080
|
}
|
|
1050
|
-
className="transition-all duration-200"
|
|
1081
|
+
className="hidden transition-all duration-200 sm:inline-flex"
|
|
1051
1082
|
onClick={() => setSyncScroll(!syncScroll)}
|
|
1052
1083
|
size="sm"
|
|
1053
1084
|
variant={syncScroll ? "secondary" : "ghost"}
|
|
@@ -1070,15 +1101,18 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
1070
1101
|
|
|
1071
1102
|
{/* Save button */}
|
|
1072
1103
|
<Button
|
|
1104
|
+
aria-label="History"
|
|
1105
|
+
className="shrink-0"
|
|
1073
1106
|
disabled={!hasLocalSnapshot}
|
|
1074
1107
|
onClick={() => setHistoryDialogOpen(true)}
|
|
1075
1108
|
size="sm"
|
|
1076
1109
|
variant="outline"
|
|
1077
1110
|
>
|
|
1078
|
-
<HistoryIcon className="mr-1.5
|
|
1079
|
-
History
|
|
1111
|
+
<HistoryIcon className="size-4 sm:mr-1.5" />
|
|
1112
|
+
<span className="hidden sm:inline">History</span>
|
|
1080
1113
|
</Button>
|
|
1081
1114
|
<Button
|
|
1115
|
+
className="shrink-0"
|
|
1082
1116
|
disabled={!hasUnsavedChanges || saveStatus === "saving"}
|
|
1083
1117
|
onClick={handleForceSave}
|
|
1084
1118
|
size="sm"
|
|
@@ -1093,6 +1127,15 @@ export default function DocumentEditor({ navigate }: PageProps) {
|
|
|
1093
1127
|
</div>
|
|
1094
1128
|
</header>
|
|
1095
1129
|
|
|
1130
|
+
{saveStatus === "error" && saveError && (
|
|
1131
|
+
<p
|
|
1132
|
+
className="border-destructive/30 border-b bg-destructive/10 px-4 py-2 text-destructive text-sm sm:hidden"
|
|
1133
|
+
role="alert"
|
|
1134
|
+
>
|
|
1135
|
+
{saveError}
|
|
1136
|
+
</p>
|
|
1137
|
+
)}
|
|
1138
|
+
|
|
1096
1139
|
{externalChangeNotice && (
|
|
1097
1140
|
<div className="border-amber-500/30 border-b bg-amber-500/10 px-4 py-3">
|
|
1098
1141
|
<div className="flex flex-wrap items-center justify-between gap-3">
|