@imenam/mcp-documentor 1.0.38 → 1.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/core/storage-engine.js +44 -5
- package/dist/src/gui-worker.js +28 -0
- package/dist/src/index.js +90 -1
- package/dist/src/tools/check-links.js +48 -6
- package/dist/src/tools/create-doc.d.ts +1 -0
- package/dist/src/tools/create-doc.js +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/gui/dist/assets/index-DoIhSuif.css +1 -0
- package/gui/dist/assets/index-Dwlj9iqX.js +53 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/gui/dist/assets/index-BRc1VJKg.js +0 -51
- package/gui/dist/assets/index-E5YNsuoG.css +0 -1
|
@@ -207,16 +207,21 @@ export class StorageEngine {
|
|
|
207
207
|
return true;
|
|
208
208
|
}
|
|
209
209
|
async rewriteLinks(oldId, newId, project) {
|
|
210
|
-
const
|
|
210
|
+
const config = this.getConfig();
|
|
211
|
+
const proj = project ?? config.currentProject;
|
|
211
212
|
const updated = [];
|
|
212
|
-
|
|
213
|
+
// Within the same project: rewrite same-project refs and explicit self-cross-project refs
|
|
214
|
+
const sameProjDocs = await this.listDocs(proj);
|
|
215
|
+
for (const summary of sameProjDocs) {
|
|
213
216
|
if (summary.id === newId)
|
|
214
217
|
continue;
|
|
215
|
-
const doc = await this.getDoc(summary.id,
|
|
218
|
+
const doc = await this.getDoc(summary.id, proj);
|
|
216
219
|
if (!doc)
|
|
217
220
|
continue;
|
|
218
221
|
let changed = false;
|
|
219
|
-
|
|
222
|
+
let newContent = doc.content.replace(new RegExp(`doc://${escapeRegex(oldId)}`, "g"), `doc://${newId}`);
|
|
223
|
+
// Also rewrite explicit self cross-project refs (doc://proj:oldId → doc://proj:newId)
|
|
224
|
+
newContent = newContent.replace(new RegExp(`doc://${escapeRegex(proj)}:${escapeRegex(oldId)}`, "g"), `doc://${proj}:${newId}`);
|
|
220
225
|
if (newContent !== doc.content) {
|
|
221
226
|
doc.content = newContent;
|
|
222
227
|
changed = true;
|
|
@@ -226,15 +231,49 @@ export class StorageEngine {
|
|
|
226
231
|
changed = true;
|
|
227
232
|
return newId;
|
|
228
233
|
}
|
|
234
|
+
if (ref === `${proj}:${oldId}`) {
|
|
235
|
+
changed = true;
|
|
236
|
+
return `${proj}:${newId}`;
|
|
237
|
+
}
|
|
229
238
|
return ref;
|
|
230
239
|
});
|
|
231
240
|
doc.related_docs = newRelated;
|
|
232
241
|
if (changed) {
|
|
233
242
|
doc.updatedAt = new Date().toISOString();
|
|
234
|
-
await this.saveDoc(doc,
|
|
243
|
+
await this.saveDoc(doc, proj);
|
|
235
244
|
updated.push(summary.id);
|
|
236
245
|
}
|
|
237
246
|
}
|
|
247
|
+
// In other projects: rewrite cross-project refs pointing to this project
|
|
248
|
+
for (const otherProj of config.projects) {
|
|
249
|
+
if (otherProj === proj)
|
|
250
|
+
continue;
|
|
251
|
+
const otherDocs = await this.listDocs(otherProj);
|
|
252
|
+
for (const summary of otherDocs) {
|
|
253
|
+
const doc = await this.getDoc(summary.id, otherProj);
|
|
254
|
+
if (!doc)
|
|
255
|
+
continue;
|
|
256
|
+
let changed = false;
|
|
257
|
+
const newContent = doc.content.replace(new RegExp(`doc://${escapeRegex(proj)}:${escapeRegex(oldId)}`, "g"), `doc://${proj}:${newId}`);
|
|
258
|
+
if (newContent !== doc.content) {
|
|
259
|
+
doc.content = newContent;
|
|
260
|
+
changed = true;
|
|
261
|
+
}
|
|
262
|
+
const newRelated = doc.related_docs.map((ref) => {
|
|
263
|
+
if (ref === `${proj}:${oldId}`) {
|
|
264
|
+
changed = true;
|
|
265
|
+
return `${proj}:${newId}`;
|
|
266
|
+
}
|
|
267
|
+
return ref;
|
|
268
|
+
});
|
|
269
|
+
doc.related_docs = newRelated;
|
|
270
|
+
if (changed) {
|
|
271
|
+
doc.updatedAt = new Date().toISOString();
|
|
272
|
+
await this.saveDoc(doc, otherProj);
|
|
273
|
+
updated.push(`${otherProj}:${summary.id}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
238
277
|
return updated;
|
|
239
278
|
}
|
|
240
279
|
}
|
package/dist/src/gui-worker.js
CHANGED
|
@@ -246,6 +246,13 @@ app.get("/api/projects", async (c) => {
|
|
|
246
246
|
return c.json({ error: result.error }, 500);
|
|
247
247
|
return c.json(result.data);
|
|
248
248
|
});
|
|
249
|
+
app.get("/api/folders", async (c) => {
|
|
250
|
+
const project = c.req.query("project");
|
|
251
|
+
const result = await ipcRequest("LIST_FOLDERS", project ? { project } : {});
|
|
252
|
+
if (result.error)
|
|
253
|
+
return c.json({ error: result.error }, 500);
|
|
254
|
+
return c.json(result.data);
|
|
255
|
+
});
|
|
249
256
|
app.put("/api/project", async (c) => {
|
|
250
257
|
const { project } = await c.req.json();
|
|
251
258
|
if (!project)
|
|
@@ -261,6 +268,27 @@ app.get("/api/docs", async (c) => {
|
|
|
261
268
|
return c.json({ error: result.error }, 500);
|
|
262
269
|
return c.json(result.data);
|
|
263
270
|
});
|
|
271
|
+
app.post("/api/doc", async (c) => {
|
|
272
|
+
try {
|
|
273
|
+
const body = await c.req.json();
|
|
274
|
+
if (!body.title || !body.content) {
|
|
275
|
+
return c.json({ error: "title and content are required" }, 400);
|
|
276
|
+
}
|
|
277
|
+
const result = await ipcRequest("CREATE_DOC", {
|
|
278
|
+
title: body.title,
|
|
279
|
+
content: body.content,
|
|
280
|
+
folder: body.folder,
|
|
281
|
+
tags: body.tags,
|
|
282
|
+
project: body.project,
|
|
283
|
+
}, 15000);
|
|
284
|
+
if (result.error)
|
|
285
|
+
return c.json({ error: result.error }, 500);
|
|
286
|
+
return c.json(result.data ?? { success: true });
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
return c.json({ error: err.message }, 500);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
264
292
|
app.get("/api/doc/*", async (c) => {
|
|
265
293
|
const id = decodeURIComponent(c.req.path.slice("/api/doc/".length));
|
|
266
294
|
const result = await ipcRequest("GET_DOC", { id });
|
package/dist/src/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { GuiLauncher, createLogger, setupLogging } from "@imenam/mcp-gui-interfa
|
|
|
11
11
|
import { StorageEngine } from "./core/storage-engine.js";
|
|
12
12
|
import { EmbeddingStore } from "./core/embedding-store.js";
|
|
13
13
|
import { EmbeddingEngine } from "./core/embedding-engine.js";
|
|
14
|
-
import { createDoc } from "./tools/create-doc.js";
|
|
14
|
+
import { createDoc, slugify } from "./tools/create-doc.js";
|
|
15
15
|
import { getDoc } from "./tools/get-doc.js";
|
|
16
16
|
import { listDocs } from "./tools/list-docs.js";
|
|
17
17
|
import { updateDoc } from "./tools/update-doc.js";
|
|
@@ -126,6 +126,12 @@ function handleIPCMessage(msg) {
|
|
|
126
126
|
else if (msg.type === "GET_DOC_VERSION") {
|
|
127
127
|
handleGetDocVersionIPC(msg);
|
|
128
128
|
}
|
|
129
|
+
else if (msg.type === "CREATE_DOC") {
|
|
130
|
+
handleCreateDocIPC(msg);
|
|
131
|
+
}
|
|
132
|
+
else if (msg.type === "LIST_FOLDERS") {
|
|
133
|
+
handleListFoldersIPC(msg);
|
|
134
|
+
}
|
|
129
135
|
else if (msg.type === "UPDATE_DOC") {
|
|
130
136
|
handleUpdateDocIPC(msg);
|
|
131
137
|
}
|
|
@@ -257,6 +263,89 @@ async function handleGetDoc(msg) {
|
|
|
257
263
|
});
|
|
258
264
|
}
|
|
259
265
|
}
|
|
266
|
+
async function handleListFoldersIPC(msg) {
|
|
267
|
+
try {
|
|
268
|
+
const project = msg.data?.project;
|
|
269
|
+
const docs = await storageEngine.listDocs(project);
|
|
270
|
+
const folders = new Set();
|
|
271
|
+
for (const doc of docs) {
|
|
272
|
+
if (doc.folder) {
|
|
273
|
+
const parts = doc.folder.split("/").filter(Boolean);
|
|
274
|
+
for (let i = 1; i <= parts.length; i++) {
|
|
275
|
+
folders.add(parts.slice(0, i).join("/"));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
broadcastToGUI({
|
|
280
|
+
type: "LIST_FOLDERS_RESPONSE",
|
|
281
|
+
correlationId: msg.correlationId,
|
|
282
|
+
data: { folders: Array.from(folders).sort() },
|
|
283
|
+
timestamp: new Date().toISOString(),
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
broadcastToGUI({
|
|
288
|
+
type: "LIST_FOLDERS_RESPONSE",
|
|
289
|
+
correlationId: msg.correlationId,
|
|
290
|
+
error: err.message,
|
|
291
|
+
timestamp: new Date().toISOString(),
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
async function handleCreateDocIPC(msg) {
|
|
296
|
+
try {
|
|
297
|
+
const { title, content, folder, tags, project } = msg.data ?? {};
|
|
298
|
+
if (!title || !content)
|
|
299
|
+
throw new Error("title and content are required");
|
|
300
|
+
const result = await createDoc(storageEngine, embeddingEngine, {
|
|
301
|
+
title,
|
|
302
|
+
content,
|
|
303
|
+
...(folder && { folder }),
|
|
304
|
+
...(tags && { tags }),
|
|
305
|
+
...(project && { project }),
|
|
306
|
+
});
|
|
307
|
+
if (result.isError) {
|
|
308
|
+
const text = result.content[0]?.type === "text" ? result.content[0].text : "Create failed";
|
|
309
|
+
broadcastToGUI({
|
|
310
|
+
type: "CREATE_DOC_RESPONSE",
|
|
311
|
+
correlationId: msg.correlationId,
|
|
312
|
+
error: text,
|
|
313
|
+
timestamp: new Date().toISOString(),
|
|
314
|
+
});
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const slug = slugify(title);
|
|
318
|
+
const id = folder ? `${folder}/${slug}` : slug;
|
|
319
|
+
broadcastToGUI({
|
|
320
|
+
type: "CREATE_DOC_RESPONSE",
|
|
321
|
+
correlationId: msg.correlationId,
|
|
322
|
+
data: { success: true, id },
|
|
323
|
+
timestamp: new Date().toISOString(),
|
|
324
|
+
});
|
|
325
|
+
const docs = await storageEngine.listDocs();
|
|
326
|
+
for (const doc of docs) {
|
|
327
|
+
const full = await storageEngine.getDoc(doc.id);
|
|
328
|
+
if (full && full.tracked_files.length > 0) {
|
|
329
|
+
const report = computeFreshness(full.tracked_files);
|
|
330
|
+
doc.isStale = report.status === "stale";
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const config = storageEngine.getConfig();
|
|
334
|
+
broadcastToGUI({
|
|
335
|
+
type: "INITIAL_STATE",
|
|
336
|
+
data: { config, docs, projects: config.projects },
|
|
337
|
+
timestamp: new Date().toISOString(),
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
broadcastToGUI({
|
|
342
|
+
type: "CREATE_DOC_RESPONSE",
|
|
343
|
+
correlationId: msg.correlationId,
|
|
344
|
+
error: err.message,
|
|
345
|
+
timestamp: new Date().toISOString(),
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
260
349
|
async function handleUpdateDocIPC(msg) {
|
|
261
350
|
try {
|
|
262
351
|
const result = await updateDoc(storageEngine, embeddingEngine, {
|
|
@@ -28,13 +28,31 @@ function extractDocLinks(content) {
|
|
|
28
28
|
}
|
|
29
29
|
return ids;
|
|
30
30
|
}
|
|
31
|
+
function parseCrossProjectRef(ref) {
|
|
32
|
+
const colonIdx = ref.indexOf(":");
|
|
33
|
+
if (colonIdx === -1)
|
|
34
|
+
return null;
|
|
35
|
+
return { project: ref.slice(0, colonIdx), docId: ref.slice(colonIdx + 1) };
|
|
36
|
+
}
|
|
31
37
|
export async function checkLinks(storageEngine, args) {
|
|
32
38
|
try {
|
|
33
39
|
const validated = CheckLinksArgsSchema.parse(args);
|
|
34
40
|
const config = storageEngine.getConfig();
|
|
35
41
|
const project = validated.project ?? config.currentProject;
|
|
42
|
+
const knownProjects = new Set(config.projects);
|
|
36
43
|
const docs = await storageEngine.listDocs(project);
|
|
37
44
|
const validIds = new Set(docs.map((d) => d.id));
|
|
45
|
+
// Cache of validIds per project to avoid re-fetching
|
|
46
|
+
const projectValidIds = new Map();
|
|
47
|
+
projectValidIds.set(project, validIds);
|
|
48
|
+
async function getValidIdsForProject(proj) {
|
|
49
|
+
if (projectValidIds.has(proj))
|
|
50
|
+
return projectValidIds.get(proj);
|
|
51
|
+
const projDocs = await storageEngine.listDocs(proj);
|
|
52
|
+
const ids = new Set(projDocs.map((d) => d.id));
|
|
53
|
+
projectValidIds.set(proj, ids);
|
|
54
|
+
return ids;
|
|
55
|
+
}
|
|
38
56
|
const brokenRefs = [];
|
|
39
57
|
const brokenDocLinks = [];
|
|
40
58
|
const brokenAnchors = [];
|
|
@@ -43,13 +61,37 @@ export async function checkLinks(storageEngine, args) {
|
|
|
43
61
|
if (!doc)
|
|
44
62
|
continue;
|
|
45
63
|
for (const ref of doc.related_docs ?? []) {
|
|
46
|
-
|
|
47
|
-
|
|
64
|
+
const cross = parseCrossProjectRef(ref);
|
|
65
|
+
if (cross) {
|
|
66
|
+
if (!knownProjects.has(cross.project)) {
|
|
67
|
+
brokenRefs.push({ sourceId: summary.id, brokenId: ref, reason: `project "${cross.project}" not found` });
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const targetIds = await getValidIdsForProject(cross.project);
|
|
71
|
+
if (!targetIds.has(cross.docId)) {
|
|
72
|
+
brokenRefs.push({ sourceId: summary.id, brokenId: ref, reason: `doc "${cross.docId}" not found in project "${cross.project}"` });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (!validIds.has(ref)) {
|
|
77
|
+
brokenRefs.push({ sourceId: summary.id, brokenId: ref, reason: "doc not found" });
|
|
48
78
|
}
|
|
49
79
|
}
|
|
50
80
|
for (const targetId of extractDocLinks(doc.content)) {
|
|
51
|
-
|
|
52
|
-
|
|
81
|
+
const cross = parseCrossProjectRef(targetId);
|
|
82
|
+
if (cross) {
|
|
83
|
+
if (!knownProjects.has(cross.project)) {
|
|
84
|
+
brokenDocLinks.push({ sourceId: summary.id, targetId, reason: `project "${cross.project}" not found` });
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const targetIds = await getValidIdsForProject(cross.project);
|
|
88
|
+
if (!targetIds.has(cross.docId)) {
|
|
89
|
+
brokenDocLinks.push({ sourceId: summary.id, targetId, reason: `doc "${cross.docId}" not found in project "${cross.project}"` });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else if (!validIds.has(targetId)) {
|
|
94
|
+
brokenDocLinks.push({ sourceId: summary.id, targetId, reason: "doc not found" });
|
|
53
95
|
}
|
|
54
96
|
}
|
|
55
97
|
const anchorLinks = extractAnchorLinks(doc.content);
|
|
@@ -75,10 +117,10 @@ export async function checkLinks(storageEngine, args) {
|
|
|
75
117
|
}
|
|
76
118
|
const lines = [];
|
|
77
119
|
for (const b of brokenRefs) {
|
|
78
|
-
lines.push(` [related_doc] ${b.sourceId} → ${b.brokenId} (
|
|
120
|
+
lines.push(` [related_doc] ${b.sourceId} → ${b.brokenId} (${b.reason})`);
|
|
79
121
|
}
|
|
80
122
|
for (const b of brokenDocLinks) {
|
|
81
|
-
lines.push(` [doc://] ${b.sourceId} → doc://${b.targetId} (
|
|
123
|
+
lines.push(` [doc://] ${b.sourceId} → doc://${b.targetId} (${b.reason})`);
|
|
82
124
|
}
|
|
83
125
|
for (const b of brokenAnchors) {
|
|
84
126
|
lines.push(` [anchor] ${b.sourceId} → #${b.anchor} (heading not found)`);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { StorageEngine } from "../core/storage-engine.js";
|
|
2
2
|
import type { EmbeddingEngine } from "../core/embedding-engine.js";
|
|
3
|
+
export declare function slugify(title: string): string;
|
|
3
4
|
export declare function createDoc(storageEngine: StorageEngine, embeddingEngine: EmbeddingEngine, args: unknown): Promise<{
|
|
4
5
|
isError: boolean;
|
|
5
6
|
content: {
|
|
@@ -3,7 +3,7 @@ import path from "path";
|
|
|
3
3
|
import { CreateDocArgsSchema } from "../../shared/schemas/doc.schema.js";
|
|
4
4
|
import { computeFileHash } from "./check-freshness.js";
|
|
5
5
|
import { snapshotSection } from "../core/section-tracker.js";
|
|
6
|
-
function slugify(title) {
|
|
6
|
+
export function slugify(title) {
|
|
7
7
|
return title
|
|
8
8
|
.normalize("NFD")
|
|
9
9
|
.replace(/[\u0300-\u036f]/g, "")
|
package/dist/src/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const APP_VERSION = "1.0.
|
|
1
|
+
export declare const APP_VERSION = "1.0.40";
|
package/dist/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated version file - do not edit manually
|
|
2
|
-
export const APP_VERSION = '1.0.
|
|
2
|
+
export const APP_VERSION = '1.0.40';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;background:#0f1117;color:#e1e4e8;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#root{width:100%;height:100vh}.dashboard{display:flex;flex-direction:column;height:100vh;background:#0f1117;color:#e1e4e8;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.dashboard-header{display:flex;align-items:center;justify-content:space-between;padding:10px 20px;background:#161b22;border-bottom:1px solid #30363d;gap:16px}.header-left{display:flex;align-items:center;gap:12px;flex-shrink:0}.header-title{font-size:18px;font-weight:600;margin:0;color:#f0f6fc}.connection-badge{font-size:11px;padding:2px 8px;border-radius:10px;font-weight:500}.connection-badge.connected{background:#1a3a2a;color:#3fb950}.connection-badge.disconnected{background:#3a1a1a;color:#f85149}.header-center{flex:1;max-width:500px}.header-right{display:flex;align-items:center;gap:8px;flex-shrink:0}.settings-btn{background:none;border:1px solid #30363d;color:#8b949e;font-size:18px;cursor:pointer;padding:4px 8px;border-radius:6px;transition:all .15s}.settings-btn:hover{background:#21262d;color:#f0f6fc}.stale-filter-btn{background:none;border:1px solid #30363d;color:#8b949e;font-size:12px;cursor:pointer;padding:4px 10px;border-radius:6px;transition:all .15s;white-space:nowrap}.stale-filter-btn:hover{background:#3d2a0066;border-color:#d29922;color:#d29922}.stale-filter-btn.active{background:#3d2a00;border-color:#d29922;color:#d29922;font-weight:600}.sidebar-toggle-btn{background:none;border:1px solid #30363d;color:#8b949e;font-size:12px;cursor:pointer;padding:4px 8px;border-radius:6px;transition:all .15s;line-height:1}.sidebar-toggle-btn:hover{background:#21262d;color:#f0f6fc}.dashboard-body{display:flex;flex:1;overflow:hidden;max-width:1700px;margin:0 auto}.sidebar-wrapper{width:450px;flex-shrink:0;overflow:hidden;transition:width .25s ease}.sidebar-wrapper.collapsed{width:0}.sidebar{width:450px;height:100%;border-right:1px solid #30363d;background:#161b22;overflow-y:auto;flex-shrink:0}.sidebar-item{padding:12px 16px;cursor:pointer;border-bottom:1px solid #21262d;transition:background .15s}.sidebar-item:hover{background:#1c2129}.sidebar-item.active{background:#1f6feb22;border-left:3px solid #1f6feb}.sidebar-item-title{font-size:14px;font-weight:500;color:#f0f6fc;margin-bottom:4px}.sidebar-item-meta{font-size:11px;color:#8b949e}.sidebar-tags{display:flex;gap:4px;flex-wrap:wrap;margin-top:4px}.tag-badge{font-size:10px;padding:1px 6px;border-radius:8px;background:#21262d;color:#8b949e}.stale-badge{font-size:10px;padding:1px 6px;border-radius:8px;font-weight:600}.stale-badge.stale{background:#3d2a00;color:#d29922}.stale-badge.fresh{background:#1a3a2a;color:#3fb950}.sidebar-empty{padding:24px 16px;text-align:center;color:#8b949e;font-size:13px}.sidebar-filter-header{display:flex;align-items:center;gap:8px;padding:10px 16px;border-bottom:1px solid #3d2a00;background:#1f1600;font-size:12px;font-weight:600;color:#d29922;text-transform:uppercase;letter-spacing:.05em;-webkit-user-select:none;user-select:none}.sidebar-filter-icon{font-size:13px}.sidebar-filter-count{margin-left:auto;background:#3d2a00;color:#d29922;font-size:11px;padding:1px 7px;border-radius:10px;font-weight:700}.sidebar-folder-header{padding:8px 16px;font-size:11px;font-weight:600;color:#8b949e;background:#0f1117;border-bottom:1px solid #21262d;cursor:pointer;display:flex;align-items:center;gap:6px;text-transform:uppercase;letter-spacing:.05em;-webkit-user-select:none;user-select:none}.sidebar-folder-header:hover{color:#c9d1d9}.folder-icon{font-size:9px;transition:transform .15s;display:inline-block}.folder-icon.open{transform:rotate(90deg)}.doc-viewer{flex:1;min-height:0;display:flex;flex-direction:column;overflow-y:auto;padding:24px 32px}.doc-viewer-empty{display:flex;align-items:center;justify-content:center;height:100%;color:#484f58;font-size:16px}.doc-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;padding-bottom:12px;border-bottom:1px solid #21262d}.doc-header-left{display:flex;align-items:center;gap:12px;min-width:0}.nav-buttons{display:flex;gap:4px;flex-shrink:0}.nav-btn{background:none;border:1px solid #30363d;color:#8b949e;font-size:16px;cursor:pointer;padding:3px 9px;border-radius:6px;transition:all .15s;line-height:1.4}.nav-btn:hover:not(:disabled){background:#21262d;color:#f0f6fc;border-color:#8b949e}.nav-btn:disabled{opacity:.3;cursor:default}.doc-title{font-size:24px;font-weight:600;color:#f0f6fc;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.doc-header-right{display:flex;align-items:center;gap:8px;flex-shrink:0}.freshness-btn{padding:6px 12px;border:1px solid #30363d;background:#21262d;color:#c9d1d9;border-radius:6px;cursor:pointer;font-size:12px;transition:all .15s}.freshness-btn:hover{background:#30363d;color:#f0f6fc}.download-btn{padding:6px 12px;border:1px solid #30363d;background:#21262d;color:#8b949e;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600;transition:all .15s;white-space:nowrap}.download-btn:hover{background:#1f6feb22;border-color:#1f6feb;color:#58a6ff}.copy-btn{padding:6px 12px;border:1px solid #30363d;background:#21262d;color:#8b949e;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600;transition:all .15s;white-space:nowrap}.copy-btn:hover{background:#1f6feb22;border-color:#1f6feb;color:#58a6ff}.copy-btn--copied,.copy-btn--copied:hover{background:#1a3a1f;border-color:#3fb950;color:#3fb950}.doc-related{display:flex;align-items:center;flex-wrap:wrap;gap:8px;padding:10px 14px;margin-bottom:20px;background:#161b22;border:1px solid #30363d;border-radius:6px;font-size:12px}.doc-related-label{color:#8b949e;font-weight:500;flex-shrink:0}.doc-related-link{padding:2px 10px;border:1px solid #1f6feb55;background:#1f6feb18;color:#58a6ff;border-radius:12px;cursor:pointer;font-size:12px;transition:all .15s}.doc-related-link:hover{background:#1f6feb33;border-color:#1f6feb}.doc-related-broken{padding:2px 10px;border:1px solid #f8514933;background:#f8514918;color:#f85149;border-radius:12px;font-size:12px;font-style:italic}.doc-related-link--cross-project{border-color:#6e40c955;background:#6e40c918;color:#d2a8ff}.doc-related-link--cross-project:hover{background:#6e40c933;border-color:#6e40c9}.doc-inline-link{display:inline;padding:0;border:none;background:none;color:#58a6ff;cursor:pointer;font-size:inherit;font-family:inherit;text-decoration:underline;text-underline-offset:2px}.doc-inline-link:hover{color:#79c0ff}.doc-inline-link--broken{color:#f85149;cursor:not-allowed;text-decoration:underline wavy}.doc-inline-link--cross-project{color:#d2a8ff}.doc-inline-link--cross-project:hover{color:#e2c0ff}.doc-content h1{font-size:2em;margin:.67em 0;border-bottom:1px solid #21262d;padding-bottom:.3em}.doc-content h2{font-size:1.5em;margin:.83em 0;border-bottom:1px solid #21262d;padding-bottom:.3em}.doc-content h3{font-size:1.25em;margin:1em 0}.doc-content h4{font-size:1em;margin:1.33em 0}.doc-content p{margin:1em 0;line-height:1.6}.doc-content code{background:#161b22;padding:2px 6px;border-radius:4px;font-family:Fira Code,Cascadia Code,monospace;font-size:.9em;font-variant-ligatures:none}.doc-content pre{background:#161b22;padding:16px;border-radius:8px;overflow-x:auto;border:1px solid #30363d}.doc-content pre code{background:none;padding:0}.doc-content ul,.doc-content ol{padding-left:2em;margin:1em 0}.doc-content li{margin:.25em 0;line-height:1.6}.doc-content table{border-collapse:collapse;width:100%;margin:1em 0}.doc-content th,.doc-content td{border:1px solid #30363d;padding:8px 12px;text-align:left}.doc-content th{background:#161b22;font-weight:600}.doc-content a{color:#58a6ff;text-decoration:none}.doc-content a:hover{text-decoration:underline}.doc-content blockquote{border-left:3px solid #30363d;padding-left:16px;margin:1em 0;color:#8b949e}.search-bar{display:flex;align-items:center;gap:8px}.search-scope-toggle{display:flex;align-items:center;gap:6px;cursor:pointer;-webkit-user-select:none;user-select:none}.search-scope-label{font-size:11px;color:#8b949e;white-space:nowrap}.toggle-switch{position:relative;display:inline-block;width:32px;height:18px;flex-shrink:0}.toggle-switch input{opacity:0;width:0;height:0}.toggle-slider{position:absolute;cursor:pointer;inset:0;background:#30363d;border-radius:18px;transition:background .2s}.toggle-slider:before{content:"";position:absolute;width:14px;height:14px;left:2px;bottom:2px;background:#8b949e;border-radius:50%;transition:transform .2s,background .2s}.toggle-switch input:checked+.toggle-slider{background:#1f6feb}.toggle-switch input:checked+.toggle-slider:before{background:#fff;transform:translate(14px)}.search-input{flex:1;padding:6px 12px;border:1px solid #30363d;background:#0d1117;color:#c9d1d9;border-radius:6px;font-size:13px;outline:none;transition:border-color .15s}.search-input:focus{border-color:#1f6feb}.search-input::placeholder{color:#484f58}.search-btn,.clear-btn{padding:6px 12px;border:1px solid #30363d;background:#21262d;color:#c9d1d9;border-radius:6px;cursor:pointer;font-size:12px;white-space:nowrap}.search-btn:hover,.clear-btn:hover{background:#30363d}.search-results{position:absolute;top:100%;left:0;right:0;background:#161b22;border:1px solid #30363d;border-radius:0 0 6px 6px;max-height:300px;overflow-y:auto;z-index:100}.search-result-item{padding:10px 12px;cursor:pointer;border-bottom:1px solid #21262d}.search-result-item:hover{background:#1c2129}.search-result-title{font-size:13px;font-weight:500;color:#f0f6fc}.search-result-score{font-size:11px;color:#3fb950;margin-left:8px}.search-result-excerpt{font-size:11px;color:#8b949e;margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.project-selector{display:flex;align-items:center;gap:4px}.project-select{padding:4px 8px;border:1px solid #30363d;background:#0d1117;color:#c9d1d9;border-radius:6px;font-size:12px;outline:none}.new-project-input{padding:4px 8px;border:1px solid #30363d;background:#0d1117;color:#c9d1d9;border-radius:6px;font-size:12px;width:100px;outline:none}.new-project-btn{padding:4px 8px;border:1px solid #30363d;background:#21262d;color:#c9d1d9;border-radius:6px;cursor:pointer;font-size:12px}.settings-overlay{position:fixed;inset:0;background:#00000080;z-index:200;display:flex;align-items:center;justify-content:center}.settings-popover{background:#161b22;border:1px solid #30363d;border-radius:12px;padding:24px;width:400px;max-width:90%}.settings-title{font-size:16px;font-weight:600;margin:0 0 16px;color:#f0f6fc}.settings-field{margin-bottom:12px}.settings-label{display:block;font-size:12px;color:#8b949e;margin-bottom:4px}.settings-input{width:100%;padding:6px 10px;border:1px solid #30363d;background:#0d1117;color:#c9d1d9;border-radius:6px;font-size:13px;outline:none;box-sizing:border-box}.settings-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:16px}.settings-save-btn{padding:6px 16px;border:none;background:#1f6feb;color:#fff;border-radius:6px;cursor:pointer;font-size:13px}.settings-cancel-btn{padding:6px 16px;border:1px solid #30363d;background:#21262d;color:#c9d1d9;border-radius:6px;cursor:pointer;font-size:13px}.diff-toggle-btn{padding:3px 10px;border:1px solid #d29922;background:#3d2a0033;color:#d29922;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600;transition:all .15s;white-space:nowrap;flex-shrink:0}.diff-toggle-btn:hover:not(:disabled){background:#3d2a0088}.diff-toggle-btn.active{background:#1a3a2a44;border-color:#3fb950;color:#3fb950}.diff-toggle-btn.active:hover{background:#1a3a2a88}.diff-toggle-btn:disabled{opacity:.5;cursor:default}.code-block-wrapper{border:1px solid #30363d;border-radius:6px;margin:12px 0;overflow:hidden}.code-block-header{display:flex;align-items:center;justify-content:space-between;padding:4px 12px;background:#21262d;border-bottom:1px solid #30363d}.code-block-lang-select{background:transparent;border:none;color:#8b949e;font-size:11px;font-family:Fira Code,Cascadia Code,Consolas,monospace;cursor:pointer;outline:none;padding:2px 0}.code-block-lang-select:hover{color:#c9d1d9}.code-block-lang-select option{background:#21262d;color:#c9d1d9}.code-block-copy-btn{background:none;border:1px solid #30363d;color:#8b949e;font-size:11px;cursor:pointer;padding:2px 8px;border-radius:4px;transition:all .15s}.code-block-copy-btn:hover{background:#30363d;color:#c9d1d9}.diff-viewer{flex:1;overflow:auto;background:#0d1117;border:1px solid #30363d;border-radius:8px;margin-top:4px}.diff-pre{margin:0;padding:0;font-family:Fira Code,Cascadia Code,Consolas,monospace;font-size:12.5px;line-height:1.55;white-space:pre;overflow:visible}.diff-line{display:block;padding:0 16px;min-height:1.55em}.diff-line--added{background:#1a3a1a;color:#7ee787}.diff-line--removed{background:#3a1a1a;color:#f47067}.diff-line--hunk{background:#0d1b2a;color:#79c0ff;font-weight:600}.diff-line--file-header{background:#161b22;color:#8b949e;font-style:italic}.diff-line--section-title{background:#1c1c2a;color:#c9d1d9;font-weight:700;padding-top:12px;padding-bottom:4px;border-top:1px solid #30363d;margin-top:4px}.diff-line--separator{background:#0f1117;color:#30363d;border-top:1px solid #21262d;border-bottom:1px solid #21262d}.diff-line--context{color:#c9d1d9}.file-status-table{width:100%;border-collapse:collapse;margin-top:12px}.file-status-table th,.file-status-table td{border:1px solid #30363d;padding:6px 10px;font-size:12px;text-align:left}.file-status-table th{background:#161b22;color:#8b949e}.status-ok{color:#3fb950}.status-modified{color:#d29922}.status-missing{color:#f85149}.history-viewer{display:flex;height:calc(100vh - 120px);gap:24px;overflow:hidden}.history-list{width:320px;flex-shrink:0;overflow-y:auto;padding-right:12px;border-right:1px solid #30363d}.history-entry{display:flex;flex-direction:column;width:100%;padding:12px;background:#161b22;border:1px solid #30363d;border-radius:6px;margin-bottom:8px;cursor:pointer;transition:all .15s;text-align:left;gap:6px}.history-entry:hover{border-color:#8b949e;background:#1c2129}.history-entry.history-entry--active{border-color:#1f6feb;background:#1f6feb11;box-shadow:0 0 0 1px #1f6feb}.history-entry-header{display:flex;align-items:center;justify-content:space-between;width:100%}.history-entry-version{font-size:13px;font-weight:600;color:#f0f6fc}.history-entry-type{font-size:10px;padding:2px 8px;border-radius:10px;font-weight:600;text-transform:uppercase;width:fit-content}.history-entry-type--create{background:#1a3a2a;color:#3fb950}.history-entry-type--update{background:#1c2a3a;color:#58a6ff}.history-entry-type--patch{background:#3d2a00;color:#d29922}.history-entry-type--delete{background:#3a1a1a;color:#f85149}.history-entry-type--snapshot{background:#1e1a3a;color:#a371f7}.history-group{display:flex;flex-direction:column}.history-entry--snapshot-card{padding:0}.history-entry-snapshot-main{display:flex;flex-direction:column;gap:4px;width:100%;padding:10px 12px;background:none;border:none;cursor:pointer;text-align:left;color:inherit}.history-entry-footer{display:flex;align-items:center;justify-content:space-between;gap:8px}.history-group-toggle{font-size:11px;color:#8b949e;cursor:pointer;white-space:nowrap;padding:2px 6px;border-radius:4px;transition:color .15s,background .15s;-webkit-user-select:none;user-select:none}.history-group-toggle:hover{color:#a371f7;background:#a371f715}.history-group-toggle--open{color:#a371f7}.history-group-entries{display:flex;flex-direction:column;margin-left:12px;border-left:2px solid #a371f730;padding-left:4px;margin-bottom:6px}.history-entry--sub{font-size:12px;padding:7px 10px}.history-entry--sub .history-entry-version{font-size:11px;color:#8b949e}.history-uncommitted{display:flex;flex-direction:column;margin-bottom:8px;border:1px dashed #30363d;border-radius:6px;overflow:hidden}.history-uncommitted-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:#8b949e;padding:5px 10px;background:#161b22;border-bottom:1px dashed #30363d}.history-entry-date{font-size:11px;color:#8b949e}.history-content{flex:1;overflow-y:auto;padding-bottom:40px}.history-content-meta{display:flex;flex-direction:column;gap:12px;padding:16px;background:#161b22;border:1px solid #30363d;border-radius:8px;margin-bottom:24px}.history-content-meta-title{display:flex;align-items:center;justify-content:space-between;width:100%}.history-content-meta-info{display:flex;align-items:center;gap:12px}.history-content-meta strong{font-size:18px;color:#f0f6fc}.history-content-version{background:#21262d;color:#8b949e;padding:2px 8px;border-radius:4px;font-size:12px;font-weight:600}.history-content-date{font-size:13px;color:#8b949e}.history-diff-loading{font-size:12px;color:#8b949e;font-style:italic}.history-diff-base{font-size:11px;color:#8b949e;background:#21262d;border:1px solid #30363d;border-radius:4px;padding:2px 7px;font-family:monospace}.history-diff-recap-banner{font-size:12px;color:#8b949e;background:#161b22;border:1px solid #30363d;border-left:3px solid #388bfd;border-radius:4px;padding:7px 12px;margin-bottom:12px}.history-meta-diff{display:flex;flex-direction:column;gap:8px;margin-top:12px;padding:12px 14px;background:#0d1117;border:1px solid #30363d;border-radius:6px}.history-meta-diff-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:#8b949e;margin-bottom:2px}.history-meta-diff-row{display:flex;align-items:center;flex-wrap:wrap;gap:6px}.history-meta-diff-field{font-size:12px;font-weight:600;color:#8b949e;min-width:90px}.history-meta-diff-arrow{font-size:12px;color:#8b949e}.history-meta-diff-chips{display:flex;flex-wrap:wrap;gap:4px}.history-meta-diff-chip--added{font-size:12px;padding:2px 8px;border-radius:4px;background:#3fb95026;color:#3fb950;border:1px solid rgba(63,185,80,.3)}.history-meta-diff-chip--removed{font-size:12px;padding:2px 8px;border-radius:4px;background:#f8514926;color:#f85149;border:1px solid rgba(248,81,73,.3);text-decoration:line-through}.history-inline-diff{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:13.5px;line-height:1.8;white-space:pre-wrap;word-break:break-word;color:#e6edf3;padding:16px}.history-inline-diff--added{background:#3fb95040;color:#3fb950;border-radius:2px}.history-inline-diff--removed{background:#f8514940;color:#f85149;border-radius:2px;text-decoration:line-through}.doc-editor{display:flex;flex-direction:column;flex:1;min-height:0;gap:12px;margin-top:4px}.doc-editor-textarea{flex:1;min-height:400px;background:#161b22;color:#e1e4e8;border:1px solid #30363d;border-radius:6px;padding:16px;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:13px;line-height:1.6;resize:vertical;outline:none}.doc-editor-textarea:focus{border-color:#388bfd}.doc-editor-actions{display:flex;align-items:center;gap:12px;justify-content:flex-end}.doc-editor-error{color:#f85149;font-size:13px;margin-right:auto}.save-btn{background:#238636;color:#fff;border:none;border-radius:6px;padding:6px 16px;font-size:13px;cursor:pointer;transition:background .15s}.save-btn:hover:not(:disabled){background:#2ea043}.save-btn:disabled{opacity:.5;cursor:not-allowed}.edit-btn{background:none;border:1px solid #30363d;color:#8b949e;border-radius:6px;padding:4px 10px;font-size:12px;cursor:pointer;transition:all .15s}.edit-btn:hover,.edit-btn.active{background:#21262d;color:#f0f6fc;border-color:#8b949e}@media print{.dashboard-header,.sidebar-wrapper,.doc-header,.doc-related,.file-status-view{display:none!important}.dashboard,.dashboard-body,.doc-viewer{display:block!important;background:#fff!important;color:#000!important;height:auto!important;overflow:visible!important}.doc-content{max-width:100%!important;padding:0!important;color:#000!important;background:#fff!important}pre,code{background:#f5f5f5!important;color:#333!important;border:1px solid #ddd!important}}.create-doc-popover{width:min(960px,95vw);max-width:95vw;max-height:90vh;display:flex;flex-direction:column}.create-doc-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px 16px;margin-bottom:12px}.create-doc-content-field{display:flex;flex-direction:column;flex:1;min-height:0}.create-doc-textarea{flex:1;min-height:380px;width:100%;box-sizing:border-box;resize:vertical;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,monospace;font-size:13px;line-height:1.5}.create-doc-hint{margin-top:4px;font-size:11px;color:#6e7681}.folder-combobox{position:relative}.folder-combobox .settings-input{padding-right:28px}.folder-combobox-toggle{position:absolute;top:50%;right:6px;transform:translateY(-50%);background:transparent;border:none;color:#8b949e;cursor:pointer;font-size:12px;padding:2px 6px;border-radius:4px;line-height:1}.folder-combobox-toggle:hover{color:#c9d1d9;background:#21262d}.folder-combobox-menu{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:10;background:#0d1117;border:1px solid #30363d;border-radius:6px;box-shadow:0 8px 24px #00000080;max-height:220px;overflow-y:auto;padding:4px 0}.folder-combobox-header{padding:6px 10px 4px;font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:#6e7681;border-bottom:1px solid #21262d;margin-bottom:4px}.folder-combobox-empty{padding:8px 10px;font-size:12px;color:#8b949e;font-style:italic}.folder-combobox-list{list-style:none;margin:0;padding:0}.folder-combobox-item{padding:6px 10px;font-size:13px;color:#c9d1d9;cursor:pointer;font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,monospace}.folder-combobox-item.active,.folder-combobox-item:hover{background:#1f2a3a;color:#f0f6fc}@media(max-width:720px){.create-doc-grid{grid-template-columns:1fr}.create-doc-textarea{min-height:260px}}
|