@youtyan/code-viewer 0.2.9 → 0.2.11
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 +42 -18
- package/dist/code-viewer.js +116 -41
- package/package.json +1 -1
- package/web/app.js +312 -17
- package/web/index.html +11 -0
- package/web/style.css +14 -0
package/README.md
CHANGED
|
@@ -27,8 +27,9 @@ Requires Node.js 20 or newer when installed from npm. Development uses
|
|
|
27
27
|
viewer.
|
|
28
28
|
- Browse SQLite, PostgreSQL, MySQL, Redis, Elasticsearch, and S3-compatible
|
|
29
29
|
object storage (MinIO, LocalStack) with a built-in datastore viewer.
|
|
30
|
-
- Read the built-in Help page for
|
|
31
|
-
|
|
30
|
+
- Read the built-in Help page for getting started, the `.code-viewer/`
|
|
31
|
+
project files, AI annotations, datastores, the agent skill, and
|
|
32
|
+
keybindings.
|
|
32
33
|
- Open repository folders (and parent folders of files) in the OS file
|
|
33
34
|
manager, create folders, and trash/restore files from localhost-only
|
|
34
35
|
actions.
|
|
@@ -115,7 +116,9 @@ rendered, and the page includes controls to copy the full file or reopen it in
|
|
|
115
116
|
the full non-virtual view.
|
|
116
117
|
|
|
117
118
|
The worktree is watched and changes are pushed to every open tab over SSE so
|
|
118
|
-
files reload as you edit.
|
|
119
|
+
files reload as you edit. The directory watcher is capped at 1024 directories
|
|
120
|
+
by default and can be tuned from Viewer Settings → **File change watcher**
|
|
121
|
+
(range slider + numeric input, 16–65536); when the cap is hit the viewer
|
|
119
122
|
shows a banner so reloads are not silently missed.
|
|
120
123
|
|
|
121
124
|
Large repositories load folder children on demand. The sidebar remembers which
|
|
@@ -129,13 +132,15 @@ views remain read-only. Open **Viewer Settings** in the header to toggle
|
|
|
129
132
|
uploads off, edit the directories to skip while browsing/searching, and hide
|
|
130
133
|
files or directory names completely.
|
|
131
134
|
|
|
132
|
-
Scope settings control
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
Scope settings control directory exclusions shared by the sidebar, Ctrl+K file
|
|
136
|
+
palette, Ctrl+G grep palette, the Datastores browser, and the file change
|
|
137
|
+
watcher — the same list applies to all five. Everything you change in Viewer
|
|
138
|
+
Settings is saved on the server under `.code-viewer/settings.json` (no
|
|
139
|
+
separate project-level config file). `.DS_Store` and a broad set of
|
|
136
140
|
build/cache directories (`node_modules`, `dist`, `build`, `.next`, `.turbo`,
|
|
137
|
-
`.venv`, …) are hidden by
|
|
138
|
-
|
|
141
|
+
`.parcel-cache`, `.vite`, `.angular`, `.dart_tool`, `.venv`, …) are hidden by
|
|
142
|
+
default. Pass `--scope-omit-dir <name>` (repeatable) to override the omit
|
|
143
|
+
list on the command line for one session.
|
|
139
144
|
|
|
140
145
|
The viewer keeps its per-project state under `.code-viewer/` at the repository
|
|
141
146
|
root:
|
|
@@ -235,9 +240,10 @@ Open Datastores in the global navigation to access:
|
|
|
235
240
|
|
|
236
241
|
### CLI
|
|
237
242
|
|
|
238
|
-
AI agents can read
|
|
239
|
-
|
|
240
|
-
|
|
243
|
+
AI agents can run read-only queries, search content across tables, and
|
|
244
|
+
capture snapshots / diffs from the command line. The same operations are
|
|
245
|
+
mirrored under the Datastores tab in the browser UI, and every CLI result is
|
|
246
|
+
written to the per-repository history visible in the browser.
|
|
241
247
|
|
|
242
248
|
```sh
|
|
243
249
|
code-viewer query exec --db data.db --sql "SELECT * FROM users LIMIT 10" \
|
|
@@ -248,6 +254,22 @@ code-viewer query exec --db app.db --sql "SELECT count(*) FROM orders" \
|
|
|
248
254
|
|
|
249
255
|
code-viewer query list --db app.db --json
|
|
250
256
|
code-viewer query clear --db app.db
|
|
257
|
+
|
|
258
|
+
code-viewer query search --db app.db --term "john@example.com" \
|
|
259
|
+
--tables users,orders --include-non-text --max-hits 20
|
|
260
|
+
|
|
261
|
+
code-viewer query snapshot create --db app.db --tables users,orders \
|
|
262
|
+
--note "Before user registration test"
|
|
263
|
+
code-viewer query snapshot list --db app.db --json
|
|
264
|
+
code-viewer query snapshot note --id snap-abc123 --note "Updated context"
|
|
265
|
+
code-viewer query snapshot delete --id snap-abc123
|
|
266
|
+
|
|
267
|
+
code-viewer query diff create --before snap-abc123 --after snap-def456 \
|
|
268
|
+
--note "User registration test"
|
|
269
|
+
code-viewer query diff tables --id diff-xyz789
|
|
270
|
+
code-viewer query diff rows --id diff-xyz789 --table users --type inserted
|
|
271
|
+
code-viewer query diff list --db app.db --json
|
|
272
|
+
code-viewer query diff delete --id diff-xyz789
|
|
251
273
|
```
|
|
252
274
|
|
|
253
275
|
`code-viewer query --help` shows all command syntax. `code-viewer query
|
|
@@ -303,9 +325,9 @@ with `--include-non-text` and `--run-search`. For AI agents,
|
|
|
303
325
|
|
|
304
326
|
Annotations are grouped into sessions and persisted in
|
|
305
327
|
`.code-viewer/annotations.json` at the repository root, so the walkthrough
|
|
306
|
-
survives reloads and server restarts.
|
|
307
|
-
|
|
308
|
-
|
|
328
|
+
survives reloads and server restarts. See **Uploads and Scope Settings**
|
|
329
|
+
above for how `.code-viewer/` is treated by the viewer and how to opt out of
|
|
330
|
+
sharing it through git.
|
|
309
331
|
|
|
310
332
|
In the browser, the annotation icon in the header opens the side panel. The
|
|
311
333
|
current explanation is rendered at the top of the panel while the code stays
|
|
@@ -337,9 +359,11 @@ write concise Markdown explanations, and how to install the bundled agent skill.
|
|
|
337
359
|
|
|
338
360
|
### Agent Skill
|
|
339
361
|
|
|
340
|
-
The package bundles [Agent Skills](https://agentskills.io)
|
|
341
|
-
open standard)
|
|
342
|
-
|
|
362
|
+
The package bundles three [Agent Skills](https://agentskills.io)
|
|
363
|
+
(the SKILL.md open standard) — `code-viewer-annotate`, `code-viewer-query`,
|
|
364
|
+
and `code-viewer-snapshot` — that teach AI coding agents when and how to use
|
|
365
|
+
`annotate`, read-only `query`, and snapshot / diff workflows. A single
|
|
366
|
+
`skill install` copies all three into the selected agent directories:
|
|
343
367
|
|
|
344
368
|
```sh
|
|
345
369
|
npx -y @youtyan/code-viewer skill install # Claude Code (.claude/skills/)
|
package/dist/code-viewer.js
CHANGED
|
@@ -1514,6 +1514,7 @@ var init_git = __esm(() => {
|
|
|
1514
1514
|
init_runtime();
|
|
1515
1515
|
DEFAULT_WORKTREE_OMIT_DIR_NAMES = [
|
|
1516
1516
|
"node_modules",
|
|
1517
|
+
"bower_components",
|
|
1517
1518
|
".venv",
|
|
1518
1519
|
"venv",
|
|
1519
1520
|
".next",
|
|
@@ -1521,6 +1522,11 @@ var init_git = __esm(() => {
|
|
|
1521
1522
|
".svelte-kit",
|
|
1522
1523
|
".astro",
|
|
1523
1524
|
".vercel",
|
|
1525
|
+
".angular",
|
|
1526
|
+
".docusaurus",
|
|
1527
|
+
".expo",
|
|
1528
|
+
".dart_tool",
|
|
1529
|
+
".serverless",
|
|
1524
1530
|
"dist",
|
|
1525
1531
|
"build",
|
|
1526
1532
|
"out",
|
|
@@ -1528,6 +1534,9 @@ var init_git = __esm(() => {
|
|
|
1528
1534
|
".gradle",
|
|
1529
1535
|
".pnpm-store",
|
|
1530
1536
|
".turbo",
|
|
1537
|
+
".parcel-cache",
|
|
1538
|
+
".vite",
|
|
1539
|
+
".webpack",
|
|
1531
1540
|
"__pycache__",
|
|
1532
1541
|
".pytest_cache",
|
|
1533
1542
|
".tox",
|
|
@@ -1537,6 +1546,7 @@ var init_git = __esm(() => {
|
|
|
1537
1546
|
"vendor",
|
|
1538
1547
|
".cache",
|
|
1539
1548
|
"coverage",
|
|
1549
|
+
".nyc_output",
|
|
1540
1550
|
"tmp",
|
|
1541
1551
|
"log",
|
|
1542
1552
|
"storage",
|
|
@@ -2765,7 +2775,7 @@ __export(exports_skill_cli, {
|
|
|
2765
2775
|
SKILL_HELP: () => SKILL_HELP,
|
|
2766
2776
|
AGENT_SKILL_DIRS: () => AGENT_SKILL_DIRS
|
|
2767
2777
|
});
|
|
2768
|
-
import { cpSync, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "node:fs";
|
|
2778
|
+
import { cpSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2 } from "node:fs";
|
|
2769
2779
|
import { homedir as homedir2 } from "node:os";
|
|
2770
2780
|
import { join as join5, resolve } from "node:path";
|
|
2771
2781
|
function parseAgentList(value) {
|
|
@@ -2821,25 +2831,34 @@ function parseSkillArgs(argv) {
|
|
|
2821
2831
|
}
|
|
2822
2832
|
return { ok: true, args: { kind: "install", agents, global, cwd } };
|
|
2823
2833
|
}
|
|
2834
|
+
function discoverBundledSkills(skillsRoot) {
|
|
2835
|
+
if (!existsSync4(skillsRoot))
|
|
2836
|
+
return [];
|
|
2837
|
+
return readdirSync2(skillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync4(join5(skillsRoot, entry.name, "SKILL.md"))).map((entry) => entry.name).sort();
|
|
2838
|
+
}
|
|
2824
2839
|
function installSkill(args, deps) {
|
|
2825
|
-
|
|
2840
|
+
const skills = discoverBundledSkills(deps.skillsRoot);
|
|
2841
|
+
if (skills.length === 0) {
|
|
2826
2842
|
return {
|
|
2827
2843
|
ok: false,
|
|
2828
|
-
error: `bundled
|
|
2844
|
+
error: `no bundled skills found under ${deps.skillsRoot}`
|
|
2829
2845
|
};
|
|
2830
2846
|
}
|
|
2831
2847
|
const base = args.global ? deps.homeDir : resolve(args.cwd ?? deps.projectDir);
|
|
2832
2848
|
const results = [];
|
|
2833
2849
|
for (const agent of args.agents) {
|
|
2834
|
-
const
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2850
|
+
for (const skill of skills) {
|
|
2851
|
+
const sourceDir = join5(deps.skillsRoot, skill);
|
|
2852
|
+
const target = join5(base, AGENT_SKILL_DIRS[agent], "skills", skill);
|
|
2853
|
+
const action = existsSync4(target) ? "updated" : "installed";
|
|
2854
|
+
try {
|
|
2855
|
+
mkdirSync2(target, { recursive: true });
|
|
2856
|
+
cpSync(sourceDir, target, { recursive: true });
|
|
2857
|
+
} catch (error) {
|
|
2858
|
+
return { ok: false, error: String(error) };
|
|
2859
|
+
}
|
|
2860
|
+
results.push({ agent, skill, action, target });
|
|
2841
2861
|
}
|
|
2842
|
-
results.push({ agent, action, target });
|
|
2843
2862
|
}
|
|
2844
2863
|
return { ok: true, results };
|
|
2845
2864
|
}
|
|
@@ -2855,7 +2874,7 @@ function runSkillCli(argv) {
|
|
|
2855
2874
|
return;
|
|
2856
2875
|
}
|
|
2857
2876
|
const result = installSkill(parsed.args, {
|
|
2858
|
-
|
|
2877
|
+
skillsRoot: join5(ROOT, "skills"),
|
|
2859
2878
|
homeDir: homedir2(),
|
|
2860
2879
|
projectDir: process.cwd()
|
|
2861
2880
|
});
|
|
@@ -2864,13 +2883,13 @@ function runSkillCli(argv) {
|
|
|
2864
2883
|
process.exit(1);
|
|
2865
2884
|
}
|
|
2866
2885
|
for (const entry of result.results) {
|
|
2867
|
-
console.log(`${entry.action} (${entry.agent}): ${entry.target}`);
|
|
2886
|
+
console.log(`${entry.action} (${entry.agent}/${entry.skill}): ${entry.target}`);
|
|
2868
2887
|
}
|
|
2869
2888
|
if (result.results.some((entry) => entry.action === "installed")) {
|
|
2870
|
-
console.log("Re-run the same command anytime to update the
|
|
2889
|
+
console.log("Re-run the same command anytime to update the skills.");
|
|
2871
2890
|
}
|
|
2872
2891
|
}
|
|
2873
|
-
var
|
|
2892
|
+
var AGENT_SKILL_DIRS, AGENT_NAMES, SKILL_HELP;
|
|
2874
2893
|
var init_skill_cli = __esm(() => {
|
|
2875
2894
|
init_root();
|
|
2876
2895
|
AGENT_SKILL_DIRS = {
|
|
@@ -2881,15 +2900,16 @@ var init_skill_cli = __esm(() => {
|
|
|
2881
2900
|
agents: ".agents"
|
|
2882
2901
|
};
|
|
2883
2902
|
AGENT_NAMES = Object.keys(AGENT_SKILL_DIRS);
|
|
2884
|
-
SKILL_HELP = `code-viewer skill — manage the bundled agent
|
|
2903
|
+
SKILL_HELP = `code-viewer skill — manage the bundled agent skills
|
|
2885
2904
|
|
|
2886
2905
|
Usage:
|
|
2887
2906
|
code-viewer skill install [--agent <list>] [--global] [--cwd <dir>]
|
|
2888
2907
|
|
|
2889
|
-
Installs
|
|
2890
|
-
skills directory of each selected agent in the current
|
|
2891
|
-
home directory equivalents with --global. Running
|
|
2892
|
-
the files, so the same command also updates an
|
|
2908
|
+
Installs every bundled skill (each SKILL.md directory under the package's
|
|
2909
|
+
skills/) into the skills directory of each selected agent in the current
|
|
2910
|
+
project, or into the home directory equivalents with --global. Running
|
|
2911
|
+
install again overwrites the files, so the same command also updates an
|
|
2912
|
+
existing installation.
|
|
2893
2913
|
|
|
2894
2914
|
Options:
|
|
2895
2915
|
--agent <list> comma separated agents: ${AGENT_NAMES.join(", ")}, or all
|
|
@@ -4410,7 +4430,7 @@ function startWorktreeUpdateWatch(options) {
|
|
|
4410
4430
|
watchDirectory(options.root, true);
|
|
4411
4431
|
return { started: watchers.size > 0, close: closeAll };
|
|
4412
4432
|
}
|
|
4413
|
-
var DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT =
|
|
4433
|
+
var DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT = 1024, MIN_WORKTREE_WATCH_DIRECTORY_LIMIT = 1, MAX_WORKTREE_WATCH_DIRECTORY_LIMIT = 65536;
|
|
4414
4434
|
var init_worktree_watcher = __esm(() => {
|
|
4415
4435
|
init_search();
|
|
4416
4436
|
});
|
|
@@ -11382,6 +11402,7 @@ Examples:
|
|
|
11382
11402
|
if (scopeOmitDirCliOverride) {
|
|
11383
11403
|
scopeOmitDirNames = scopeOmitDirCliOverride;
|
|
11384
11404
|
}
|
|
11405
|
+
scopeWatchLimit = worktreeWatchDirectoryLimitFromEnv();
|
|
11385
11406
|
}
|
|
11386
11407
|
function warnIfLegacyConfigPresent() {
|
|
11387
11408
|
try {
|
|
@@ -11391,6 +11412,9 @@ function warnIfLegacyConfigPresent() {
|
|
|
11391
11412
|
} catch {}
|
|
11392
11413
|
}
|
|
11393
11414
|
function applyPersistedSettings(state) {
|
|
11415
|
+
const prevOmit = scopeOmitDirNames;
|
|
11416
|
+
const prevExclude = scopeExcludeNames;
|
|
11417
|
+
const prevWatchLimit = scopeWatchLimit;
|
|
11394
11418
|
if (!scopeOmitDirCliOverride && Array.isArray(state.scopeOmitDirs) && state.scopeOmitDirs.length > 0) {
|
|
11395
11419
|
scopeOmitDirNames = state.scopeOmitDirs;
|
|
11396
11420
|
} else if (!scopeOmitDirCliOverride) {
|
|
@@ -11401,7 +11425,26 @@ function applyPersistedSettings(state) {
|
|
|
11401
11425
|
} else {
|
|
11402
11426
|
scopeExcludeNames = DEFAULT_EXCLUDE_NAMES;
|
|
11403
11427
|
}
|
|
11428
|
+
if (state.scopeWatchLimit != null) {
|
|
11429
|
+
scopeWatchLimit = normalizeScopeWatchLimit(state.scopeWatchLimit);
|
|
11430
|
+
}
|
|
11404
11431
|
uploadEnabled = state.uploadEnabled !== false;
|
|
11432
|
+
if (prevOmit !== scopeOmitDirNames || prevExclude !== scopeExcludeNames || prevWatchLimit !== scopeWatchLimit) {
|
|
11433
|
+
restartWorktreeWatch();
|
|
11434
|
+
}
|
|
11435
|
+
}
|
|
11436
|
+
function normalizeScopeWatchLimit(value) {
|
|
11437
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
11438
|
+
return DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT;
|
|
11439
|
+
}
|
|
11440
|
+
const floored = Math.floor(value);
|
|
11441
|
+
if (floored < MIN_WORKTREE_WATCH_DIRECTORY_LIMIT) {
|
|
11442
|
+
return MIN_WORKTREE_WATCH_DIRECTORY_LIMIT;
|
|
11443
|
+
}
|
|
11444
|
+
if (floored > MAX_WORKTREE_WATCH_DIRECTORY_LIMIT) {
|
|
11445
|
+
return MAX_WORKTREE_WATCH_DIRECTORY_LIMIT;
|
|
11446
|
+
}
|
|
11447
|
+
return floored;
|
|
11405
11448
|
}
|
|
11406
11449
|
function json2(data, init = {}) {
|
|
11407
11450
|
return new Response(JSON.stringify(data), {
|
|
@@ -11883,7 +11926,11 @@ function handleSettings() {
|
|
|
11883
11926
|
omit_dirs_built_in: DEFAULT_WORKTREE_OMIT_DIR_NAMES,
|
|
11884
11927
|
exclude_names_effective: scopeExcludeNames,
|
|
11885
11928
|
exclude_names_built_in: DEFAULT_EXCLUDE_NAMES,
|
|
11886
|
-
max_entries: WORKTREE_RECURSIVE_ENTRY_LIMIT
|
|
11929
|
+
max_entries: WORKTREE_RECURSIVE_ENTRY_LIMIT,
|
|
11930
|
+
watch_limit_effective: scopeWatchLimit,
|
|
11931
|
+
watch_limit_default: DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT,
|
|
11932
|
+
watch_limit_min: MIN_WORKTREE_WATCH_DIRECTORY_LIMIT,
|
|
11933
|
+
watch_limit_max: MAX_WORKTREE_WATCH_DIRECTORY_LIMIT
|
|
11887
11934
|
}
|
|
11888
11935
|
});
|
|
11889
11936
|
}
|
|
@@ -11892,7 +11939,14 @@ function worktreeWatchDirectoryLimitFromEnv() {
|
|
|
11892
11939
|
if (!raw)
|
|
11893
11940
|
return DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT;
|
|
11894
11941
|
const parsed = Number(raw);
|
|
11895
|
-
|
|
11942
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
11943
|
+
return DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT;
|
|
11944
|
+
const floored = Math.floor(parsed);
|
|
11945
|
+
if (floored < MIN_WORKTREE_WATCH_DIRECTORY_LIMIT)
|
|
11946
|
+
return MIN_WORKTREE_WATCH_DIRECTORY_LIMIT;
|
|
11947
|
+
if (floored > MAX_WORKTREE_WATCH_DIRECTORY_LIMIT)
|
|
11948
|
+
return MAX_WORKTREE_WATCH_DIRECTORY_LIMIT;
|
|
11949
|
+
return floored;
|
|
11896
11950
|
}
|
|
11897
11951
|
function handleFiles2(url) {
|
|
11898
11952
|
const target = url.searchParams.get("ref") || url.searchParams.get("target") || "worktree";
|
|
@@ -13033,7 +13087,43 @@ async function shutdown(exitCode = 0) {
|
|
|
13033
13087
|
}
|
|
13034
13088
|
process.exit(exitCode);
|
|
13035
13089
|
}
|
|
13036
|
-
|
|
13090
|
+
function startScopedWorktreeWatch() {
|
|
13091
|
+
watchLimitReached = null;
|
|
13092
|
+
return startWorktreeUpdateWatch({
|
|
13093
|
+
root: cwd,
|
|
13094
|
+
omitDirNames: scopeOmitDirNames,
|
|
13095
|
+
excludeNames: scopeExcludeNames,
|
|
13096
|
+
watch,
|
|
13097
|
+
initialScanMode: "async",
|
|
13098
|
+
maxWatchedDirectories: scopeWatchLimit,
|
|
13099
|
+
onUpdate: triggerUpdate,
|
|
13100
|
+
onWatchLimit: (limit) => {
|
|
13101
|
+
watchLimitReached = limit;
|
|
13102
|
+
sendSse("watch-limit", String(limit));
|
|
13103
|
+
},
|
|
13104
|
+
onError: (error) => {
|
|
13105
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
13106
|
+
console.warn(`code-viewer worktree watch skipped: ${message}`);
|
|
13107
|
+
}
|
|
13108
|
+
});
|
|
13109
|
+
}
|
|
13110
|
+
function restartWorktreeWatch() {
|
|
13111
|
+
try {
|
|
13112
|
+
if (shuttingDown)
|
|
13113
|
+
return;
|
|
13114
|
+
if (!worktreeWatch)
|
|
13115
|
+
return;
|
|
13116
|
+
} catch {
|
|
13117
|
+
return;
|
|
13118
|
+
}
|
|
13119
|
+
try {
|
|
13120
|
+
worktreeWatch.close();
|
|
13121
|
+
} catch (error) {
|
|
13122
|
+
console.warn(`code-viewer worktree watch restart close skipped: ${String(error)}`);
|
|
13123
|
+
}
|
|
13124
|
+
worktreeWatch = startScopedWorktreeWatch();
|
|
13125
|
+
}
|
|
13126
|
+
var WEB_ROOT, VERSION, DEFAULT_ARGS, PREVIEW_HUNKS_DEFAULT = 3, PREVIEW_LINES_DEFAULT = 1200, WATCHED_ASSET_FILES, SIZE_SMALL = 2000, SIZE_MEDIUM = 8000, SIZE_LARGE = 20000, LINE_INDEX_MIN_START = 1e4, LINE_INDEX_MAX_FILE_BYTES, BLOB_LINE_CACHE_MAX_BYTES, MAX_UPLOAD_FILE_BYTES, MAX_UPLOAD_TOTAL_BYTES, MAX_UPLOAD_BODY_BYTES, MAX_UPLOAD_FILES = 50, SAFE_UPLOAD_EXTENSIONS, generation = 1, cwd, cliArgs, listenPort = 0, openAfterStart = false, scopeOmitDirNames, scopeOmitDirCliOverride = null, scopeExcludeNames, scopeWatchLimit, uploadEnabled = true, rgAvailableCache = null, enc, sseClients, sseKeepalives, fileCache, metaCache, fileListCache, lineIndexCache, blobLineIndexCache, blobBytesCache, blobLineCacheBytes = 0, isCodeViewerInternalPath, watchLimitReached = null, server, worktreeWatch = null, shuttingDown = false;
|
|
13037
13127
|
var init_preview = __esm(async () => {
|
|
13038
13128
|
init_routes();
|
|
13039
13129
|
init_annotations();
|
|
@@ -13096,6 +13186,7 @@ var init_preview = __esm(async () => {
|
|
|
13096
13186
|
cliArgs = DEFAULT_ARGS;
|
|
13097
13187
|
scopeOmitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES;
|
|
13098
13188
|
scopeExcludeNames = DEFAULT_EXCLUDE_NAMES;
|
|
13189
|
+
scopeWatchLimit = DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT;
|
|
13099
13190
|
enc = new TextEncoder;
|
|
13100
13191
|
sseClients = new Set;
|
|
13101
13192
|
sseKeepalives = new Map;
|
|
@@ -13248,23 +13339,7 @@ data: ${watchLimitReached}
|
|
|
13248
13339
|
watch,
|
|
13249
13340
|
sendReload: () => sendSse("reload")
|
|
13250
13341
|
});
|
|
13251
|
-
worktreeWatch =
|
|
13252
|
-
root: cwd,
|
|
13253
|
-
omitDirNames: scopeOmitDirNames,
|
|
13254
|
-
excludeNames: scopeExcludeNames,
|
|
13255
|
-
watch,
|
|
13256
|
-
initialScanMode: "async",
|
|
13257
|
-
maxWatchedDirectories: worktreeWatchDirectoryLimitFromEnv(),
|
|
13258
|
-
onUpdate: triggerUpdate,
|
|
13259
|
-
onWatchLimit: (limit) => {
|
|
13260
|
-
watchLimitReached = limit;
|
|
13261
|
-
sendSse("watch-limit", String(limit));
|
|
13262
|
-
},
|
|
13263
|
-
onError: (error) => {
|
|
13264
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
13265
|
-
console.warn(`code-viewer worktree watch skipped: ${message}`);
|
|
13266
|
-
}
|
|
13267
|
-
});
|
|
13342
|
+
worktreeWatch = startScopedWorktreeWatch();
|
|
13268
13343
|
console.log(`GDP_LISTEN_URL=http://127.0.0.1:${server.port}/`);
|
|
13269
13344
|
console.log(`git-diff-preview serving ${cwd}`);
|
|
13270
13345
|
});
|
package/package.json
CHANGED
package/web/app.js
CHANGED
|
@@ -18383,6 +18383,7 @@ ${frontmatter.yaml}
|
|
|
18383
18383
|
var HELP_LANGUAGES = ["en", "ja"];
|
|
18384
18384
|
var HELP_SECTIONS = [
|
|
18385
18385
|
"overview",
|
|
18386
|
+
"storage",
|
|
18386
18387
|
"annotations",
|
|
18387
18388
|
"database",
|
|
18388
18389
|
"skills",
|
|
@@ -18461,6 +18462,73 @@ ${frontmatter.yaml}
|
|
|
18461
18462
|
}
|
|
18462
18463
|
]
|
|
18463
18464
|
},
|
|
18465
|
+
storage: {
|
|
18466
|
+
nav: "Project Files",
|
|
18467
|
+
title: ".code-viewer Directory",
|
|
18468
|
+
intro: "code-viewer keeps every per-repository state file under .code-viewer/ at the root of the opened repository. The directory is created on demand, can be deleted at any time to reset state, and should be gitignored in most projects.",
|
|
18469
|
+
groups: [
|
|
18470
|
+
{
|
|
18471
|
+
title: "What lives there",
|
|
18472
|
+
blocks: [
|
|
18473
|
+
{
|
|
18474
|
+
kind: "table",
|
|
18475
|
+
rows: [
|
|
18476
|
+
[
|
|
18477
|
+
"settings.json",
|
|
18478
|
+
"Viewer Settings — diff layout, theme, language, sidebar/history widths, font sizes, syntax highlight, ignore-whitespace, hide-tests, scope overrides (omitted dirs / excluded names), upload toggle, annotation panel open/follow/mute/rate, and the last viewed diff range."
|
|
18479
|
+
],
|
|
18480
|
+
[
|
|
18481
|
+
"view-state.json",
|
|
18482
|
+
"Sidebar tree state — collapsed directories, lazy-expanded directories (folders opened on demand in large repos), and the viewed-file list used to dim already-read entries."
|
|
18483
|
+
],
|
|
18484
|
+
[
|
|
18485
|
+
"tabs.json",
|
|
18486
|
+
"Multi-DB tab layout — open datastore tabs, the active tab, per-tab selected table / view / SQL draft, per-tab Elasticsearch index and Redis DB index, and per-tab sidebar and history panel widths."
|
|
18487
|
+
],
|
|
18488
|
+
[
|
|
18489
|
+
"db-ui.json",
|
|
18490
|
+
"Datastore UI preferences — column widths per (DB, table, column) and toggle states such as Rails FK inference and the S3 tooltip."
|
|
18491
|
+
],
|
|
18492
|
+
[
|
|
18493
|
+
"annotations.json",
|
|
18494
|
+
"AI annotation walkthroughs — sessions with their ordered steps (file, line range, title, body), used by the annotation panel and synced live to open tabs over SSE."
|
|
18495
|
+
],
|
|
18496
|
+
[
|
|
18497
|
+
"query-history.json",
|
|
18498
|
+
"SQL query history — recent statements with column list, preview rows, row count, elapsed time, executor (browser or CLI), and execution timestamp."
|
|
18499
|
+
],
|
|
18500
|
+
[
|
|
18501
|
+
"db-snapshots.sqlite (+ -shm / -wal)",
|
|
18502
|
+
"SQLite store used by the Datastore Snapshot tab to keep point-in-time captures of tables / indices / key spaces for diffing. The -shm / -wal sidecar files are SQLite WAL artifacts; do not edit them directly."
|
|
18503
|
+
]
|
|
18504
|
+
]
|
|
18505
|
+
}
|
|
18506
|
+
]
|
|
18507
|
+
},
|
|
18508
|
+
{
|
|
18509
|
+
title: "Source of truth and editing",
|
|
18510
|
+
blocks: [
|
|
18511
|
+
{
|
|
18512
|
+
kind: "paragraph",
|
|
18513
|
+
text: "Everything in .code-viewer/ is owned by code-viewer. JSON files are rewritten safely with validation on every change, so unknown keys are dropped and invalid values are reset to defaults. Hand-edit at your own risk — a corrupt file is renamed with a .corrupt suffix and replaced with an empty default."
|
|
18514
|
+
},
|
|
18515
|
+
{
|
|
18516
|
+
kind: "paragraph",
|
|
18517
|
+
text: "Deleting the whole directory is the supported way to reset all per-repository state. Removing a single file resets only that subsystem (for example, deleting tabs.json closes all DB tabs on the next load)."
|
|
18518
|
+
}
|
|
18519
|
+
]
|
|
18520
|
+
},
|
|
18521
|
+
{
|
|
18522
|
+
title: "Sharing across machines",
|
|
18523
|
+
blocks: [
|
|
18524
|
+
{
|
|
18525
|
+
kind: "paragraph",
|
|
18526
|
+
text: "These files contain local UI state and should normally stay out of version control — add .code-viewer/ to .gitignore. To share AI walkthroughs, commit annotations.json explicitly (or copy it between checkouts) and leave the other files ignored."
|
|
18527
|
+
}
|
|
18528
|
+
]
|
|
18529
|
+
}
|
|
18530
|
+
]
|
|
18531
|
+
},
|
|
18464
18532
|
annotations: {
|
|
18465
18533
|
nav: "AI Annotations",
|
|
18466
18534
|
title: "AI Code Annotations",
|
|
@@ -18683,7 +18751,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
18683
18751
|
blocks: [
|
|
18684
18752
|
{
|
|
18685
18753
|
kind: "paragraph",
|
|
18686
|
-
text: "AI agents can execute read-only queries and
|
|
18754
|
+
text: "AI agents can execute read-only queries, search across tables, and capture snapshots / diffs from the CLI. Results are written to the same per-repository history visible in the browser, and the browser UI exposes the same operations through the Search tab and the Snapshot tab."
|
|
18687
18755
|
},
|
|
18688
18756
|
{
|
|
18689
18757
|
kind: "command",
|
|
@@ -18700,6 +18768,37 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
18700
18768
|
title: "List or clear query history",
|
|
18701
18769
|
command: `code-viewer query list --db app.db --json
|
|
18702
18770
|
code-viewer query clear --db app.db`
|
|
18771
|
+
},
|
|
18772
|
+
{
|
|
18773
|
+
kind: "command",
|
|
18774
|
+
title: "Search across all tables",
|
|
18775
|
+
command: 'code-viewer query search --db app.db --term "john@example.com" \\\n --tables users,orders --include-non-text --max-hits 20'
|
|
18776
|
+
},
|
|
18777
|
+
{
|
|
18778
|
+
kind: "command",
|
|
18779
|
+
title: "Capture a snapshot",
|
|
18780
|
+
command: 'code-viewer query snapshot create --db app.db --tables users,orders \\\n --note "Before user registration test"'
|
|
18781
|
+
},
|
|
18782
|
+
{
|
|
18783
|
+
kind: "command",
|
|
18784
|
+
title: "List, annotate, or delete snapshots",
|
|
18785
|
+
command: `code-viewer query snapshot list --db app.db --json
|
|
18786
|
+
code-viewer query snapshot note --id snap-abc123 --note "Updated context"
|
|
18787
|
+
code-viewer query snapshot delete --id snap-abc123`
|
|
18788
|
+
},
|
|
18789
|
+
{
|
|
18790
|
+
kind: "command",
|
|
18791
|
+
title: "Diff two snapshots",
|
|
18792
|
+
command: `code-viewer query diff create --before snap-abc123 --after snap-def456 \\
|
|
18793
|
+
--note "User registration test"
|
|
18794
|
+
code-viewer query diff list --db app.db --json
|
|
18795
|
+
code-viewer query diff delete --id diff-xyz789`
|
|
18796
|
+
},
|
|
18797
|
+
{
|
|
18798
|
+
kind: "command",
|
|
18799
|
+
title: "Inspect a diff",
|
|
18800
|
+
command: `code-viewer query diff tables --id diff-xyz789
|
|
18801
|
+
code-viewer query diff rows --id diff-xyz789 --table users --type inserted --limit 50`
|
|
18703
18802
|
},
|
|
18704
18803
|
{
|
|
18705
18804
|
kind: "command",
|
|
@@ -18713,10 +18812,10 @@ code-viewer query clear --db app.db`
|
|
|
18713
18812
|
skills: {
|
|
18714
18813
|
nav: "Agent Skill",
|
|
18715
18814
|
title: "Agent Skill Setup",
|
|
18716
|
-
intro: "The package
|
|
18815
|
+
intro: "The package bundles three skills — code-viewer-annotate, code-viewer-query, and code-viewer-snapshot — so AI agents know when and how to create browser walkthroughs, run read-only queries, and capture snapshot / diff sessions. A single skill install command copies all three into the selected agent directories.",
|
|
18717
18816
|
groups: [
|
|
18718
18817
|
{
|
|
18719
|
-
title: "Install the
|
|
18818
|
+
title: "Install the skills",
|
|
18720
18819
|
blocks: [
|
|
18721
18820
|
{
|
|
18722
18821
|
kind: "command",
|
|
@@ -18738,11 +18837,17 @@ code-viewer query clear --db app.db`
|
|
|
18738
18837
|
kind: "command",
|
|
18739
18838
|
title: "Install globally for a user (not per project)",
|
|
18740
18839
|
command: "npx -y @youtyan/code-viewer skill install --agent all --global"
|
|
18840
|
+
},
|
|
18841
|
+
{
|
|
18842
|
+
kind: "command",
|
|
18843
|
+
title: "Install into a different project directory",
|
|
18844
|
+
command: `npx -y @youtyan/code-viewer skill install --agent all --cwd /path/to/other/repo
|
|
18845
|
+
# --cwd selects the target project; ignored when --global is also given.`
|
|
18741
18846
|
}
|
|
18742
18847
|
]
|
|
18743
18848
|
},
|
|
18744
18849
|
{
|
|
18745
|
-
title: "What the
|
|
18850
|
+
title: "What the skills teach",
|
|
18746
18851
|
blocks: [
|
|
18747
18852
|
{
|
|
18748
18853
|
kind: "table",
|
|
@@ -18916,6 +19021,73 @@ code-viewer query clear --db app.db`
|
|
|
18916
19021
|
}
|
|
18917
19022
|
]
|
|
18918
19023
|
},
|
|
19024
|
+
storage: {
|
|
19025
|
+
nav: "プロジェクトファイル",
|
|
19026
|
+
title: ".code-viewer ディレクトリ",
|
|
19027
|
+
intro: "code-viewer はリポジトリ単位の状態をすべて、開いたリポジトリ直下の .code-viewer/ ディレクトリに保存します。必要になったときに自動で作られ、削除すれば全状態をリセットでき、通常は .gitignore に登録しておきます。",
|
|
19028
|
+
groups: [
|
|
19029
|
+
{
|
|
19030
|
+
title: "中に置かれるファイル",
|
|
19031
|
+
blocks: [
|
|
19032
|
+
{
|
|
19033
|
+
kind: "table",
|
|
19034
|
+
rows: [
|
|
19035
|
+
[
|
|
19036
|
+
"settings.json",
|
|
19037
|
+
"Viewer Settings — diff レイアウト、テーマ、言語、サイドバー/履歴幅、フォントサイズ、シンタックスハイライト、whitespace 無視、テスト非表示、scope 上書き(除外ディレクトリ / 除外名)、アップロード許可、注釈パネルの開閉/follow/ミュート/再生速度、最後に表示した diff 範囲を保存します。"
|
|
19038
|
+
],
|
|
19039
|
+
[
|
|
19040
|
+
"view-state.json",
|
|
19041
|
+
"サイドバーツリーの状態 — 折りたたみ済みディレクトリ、遅延展開済みディレクトリ(大規模リポジトリで必要に応じて開かれたフォルダ)、既読扱いするための表示済みファイル一覧。"
|
|
19042
|
+
],
|
|
19043
|
+
[
|
|
19044
|
+
"tabs.json",
|
|
19045
|
+
"マルチ DB タブのレイアウト — 開いているデータストアタブ、アクティブタブ、タブごとの選択テーブル / ビュー / SQL ドラフト、Elasticsearch のインデックスや Redis の DB index、タブ単位のサイドバー幅・履歴パネル高。"
|
|
19046
|
+
],
|
|
19047
|
+
[
|
|
19048
|
+
"db-ui.json",
|
|
19049
|
+
"データストア UI の設定 — (DB, テーブル, カラム) ごとの列幅、Rails FK 推測や S3 ツールチップなどのトグル状態。"
|
|
19050
|
+
],
|
|
19051
|
+
[
|
|
19052
|
+
"annotations.json",
|
|
19053
|
+
"AI 注釈のウォークスルー — セッションと順序付きステップ(ファイル、行範囲、タイトル、本文)。注釈パネルが読み、SSE で開いているタブへもライブ同期されます。"
|
|
19054
|
+
],
|
|
19055
|
+
[
|
|
19056
|
+
"query-history.json",
|
|
19057
|
+
"SQL クエリ履歴 — 直近のクエリ、カラム一覧、プレビュー行、行数、実行時間、実行元(browser / CLI)、実行時刻。"
|
|
19058
|
+
],
|
|
19059
|
+
[
|
|
19060
|
+
"db-snapshots.sqlite (+ -shm / -wal)",
|
|
19061
|
+
"Snapshot タブが使う SQLite ストア。テーブル / インデックス / キー空間の時点スナップショットを保持し、差分表示に使います。-shm / -wal は SQLite の WAL 用付随ファイル。手動編集しないでください。"
|
|
19062
|
+
]
|
|
19063
|
+
]
|
|
19064
|
+
}
|
|
19065
|
+
]
|
|
19066
|
+
},
|
|
19067
|
+
{
|
|
19068
|
+
title: "ファイルの所有権と編集",
|
|
19069
|
+
blocks: [
|
|
19070
|
+
{
|
|
19071
|
+
kind: "paragraph",
|
|
19072
|
+
text: ".code-viewer/ の中身はすべて code-viewer が管理します。JSON ファイルは書き込みごとにバリデーションを通して安全に書き換えられるため、未知のキーは破棄され、不正な値は既定値に戻されます。手で編集すると壊れる可能性があり、壊れたファイルは .corrupt サフィックスに改名されて空の既定値で置き換えられます。"
|
|
19073
|
+
},
|
|
19074
|
+
{
|
|
19075
|
+
kind: "paragraph",
|
|
19076
|
+
text: "ディレクトリごと削除するのが、このリポジトリの全状態をリセットする推奨手順です。個別ファイルだけを消した場合はその系統だけがリセットされます(例: tabs.json を消すと次回起動時に DB タブがすべて閉じた状態になります)。"
|
|
19077
|
+
}
|
|
19078
|
+
]
|
|
19079
|
+
},
|
|
19080
|
+
{
|
|
19081
|
+
title: "他マシンとの共有",
|
|
19082
|
+
blocks: [
|
|
19083
|
+
{
|
|
19084
|
+
kind: "paragraph",
|
|
19085
|
+
text: "これらはローカル UI 状態なので、原則としてバージョン管理には入れません — .code-viewer/ を .gitignore に追加しておきます。AI のウォークスルーを共有したい場合は annotations.json だけを明示的に commit(または別チェックアウトへコピー)し、それ以外は無視したままにします。"
|
|
19086
|
+
}
|
|
19087
|
+
]
|
|
19088
|
+
}
|
|
19089
|
+
]
|
|
19090
|
+
},
|
|
18919
19091
|
annotations: {
|
|
18920
19092
|
nav: "AI注釈",
|
|
18921
19093
|
title: "AI コード注釈",
|
|
@@ -19138,7 +19310,7 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
19138
19310
|
blocks: [
|
|
19139
19311
|
{
|
|
19140
19312
|
kind: "paragraph",
|
|
19141
|
-
text: "AI エージェントは CLI から read-only
|
|
19313
|
+
text: "AI エージェントは CLI から read-only クエリの実行、テーブル横断検索、スナップショット / 差分作成までできます。結果はブラウザで見えるのと同じリポジトリ単位の履歴に保存され、ブラウザ UI 側でも Search タブ / Snapshot タブから同じ操作が行えます。"
|
|
19142
19314
|
},
|
|
19143
19315
|
{
|
|
19144
19316
|
kind: "command",
|
|
@@ -19152,9 +19324,40 @@ code-viewer annotate add-db --db app.db --tab query \\
|
|
|
19152
19324
|
},
|
|
19153
19325
|
{
|
|
19154
19326
|
kind: "command",
|
|
19155
|
-
title: "
|
|
19327
|
+
title: "履歴の一覧 / 削除",
|
|
19156
19328
|
command: `code-viewer query list --db app.db --json
|
|
19157
19329
|
code-viewer query clear --db app.db`
|
|
19330
|
+
},
|
|
19331
|
+
{
|
|
19332
|
+
kind: "command",
|
|
19333
|
+
title: "テーブル横断の全文検索",
|
|
19334
|
+
command: 'code-viewer query search --db app.db --term "john@example.com" \\\n --tables users,orders --include-non-text --max-hits 20'
|
|
19335
|
+
},
|
|
19336
|
+
{
|
|
19337
|
+
kind: "command",
|
|
19338
|
+
title: "スナップショットを作成",
|
|
19339
|
+
command: 'code-viewer query snapshot create --db app.db --tables users,orders \\\n --note "ユーザー登録テスト前"'
|
|
19340
|
+
},
|
|
19341
|
+
{
|
|
19342
|
+
kind: "command",
|
|
19343
|
+
title: "スナップショットの一覧 / メモ更新 / 削除",
|
|
19344
|
+
command: `code-viewer query snapshot list --db app.db --json
|
|
19345
|
+
code-viewer query snapshot note --id snap-abc123 --note "メモを更新"
|
|
19346
|
+
code-viewer query snapshot delete --id snap-abc123`
|
|
19347
|
+
},
|
|
19348
|
+
{
|
|
19349
|
+
kind: "command",
|
|
19350
|
+
title: "2 つのスナップショットを diff",
|
|
19351
|
+
command: `code-viewer query diff create --before snap-abc123 --after snap-def456 \\
|
|
19352
|
+
--note "ユーザー登録テストの差分"
|
|
19353
|
+
code-viewer query diff list --db app.db --json
|
|
19354
|
+
code-viewer query diff delete --id diff-xyz789`
|
|
19355
|
+
},
|
|
19356
|
+
{
|
|
19357
|
+
kind: "command",
|
|
19358
|
+
title: "diff の中身を確認",
|
|
19359
|
+
command: `code-viewer query diff tables --id diff-xyz789
|
|
19360
|
+
code-viewer query diff rows --id diff-xyz789 --table users --type inserted --limit 50`
|
|
19158
19361
|
},
|
|
19159
19362
|
{
|
|
19160
19363
|
kind: "command",
|
|
@@ -19168,7 +19371,7 @@ code-viewer query clear --db app.db`
|
|
|
19168
19371
|
skills: {
|
|
19169
19372
|
nav: "スキル登録",
|
|
19170
19373
|
title: "Agent Skill の登録",
|
|
19171
|
-
intro: "
|
|
19374
|
+
intro: "このパッケージには 3 つのスキル (code-viewer-annotate / code-viewer-query / code-viewer-snapshot) が同梱されており、AI エージェントに annotate でのウォークスルー、read-only な query、スナップショット / 差分の使い分けを教えます。skill install を 1 回叩くと、選んだエージェントすべてに 3 スキルがコピーされます。",
|
|
19172
19375
|
groups: [
|
|
19173
19376
|
{
|
|
19174
19377
|
title: "スキルをインストールする",
|
|
@@ -19193,6 +19396,12 @@ code-viewer query clear --db app.db`
|
|
|
19193
19396
|
kind: "command",
|
|
19194
19397
|
title: "ユーザー全体(プロジェクト外)へ登録",
|
|
19195
19398
|
command: "npx -y @youtyan/code-viewer skill install --agent all --global"
|
|
19399
|
+
},
|
|
19400
|
+
{
|
|
19401
|
+
kind: "command",
|
|
19402
|
+
title: "別プロジェクトのディレクトリへ登録",
|
|
19403
|
+
command: `npx -y @youtyan/code-viewer skill install --agent all --cwd /path/to/other/repo
|
|
19404
|
+
# --cwd でインストール先プロジェクトを指定。--global を併用したときは無視されます。`
|
|
19196
19405
|
}
|
|
19197
19406
|
]
|
|
19198
19407
|
},
|
|
@@ -20313,7 +20522,7 @@ code-viewer query clear --db app.db`
|
|
|
20313
20522
|
} else {
|
|
20314
20523
|
lnHtml = '<td class="d2h-code-linenumber d2h-cntx">' + '<div class="line-num1">' + (oldStart + i2) + "</div>" + '<div class="line-num2">' + (newStart + i2) + "</div>" + "</td>";
|
|
20315
20524
|
}
|
|
20316
|
-
tr.innerHTML = lnHtml + '<td class="d2h-cntx">' + '<div class="' + (isSplit ? "d2h-code-side-line" : "d2h-code-line") + '">' + '<span class="d2h-code-line-prefix"> </span>' + '<span class="d2h-code-line-ctn">' + escapeHtmlText(lines[i2]) + "</span>" + "</div>" + "</td>";
|
|
20525
|
+
tr.innerHTML = lnHtml + '<td class="d2h-cntx">' + '<div class="' + (isSplit ? "d2h-code-side-line" : "d2h-code-line") + '">' + '<span class="d2h-code-line-prefix"> </span> ' + '<span class="d2h-code-line-ctn">' + escapeHtmlText(lines[i2]) + "</span>" + "</div>" + "</td>";
|
|
20317
20526
|
frag.appendChild(tr);
|
|
20318
20527
|
}
|
|
20319
20528
|
tbody.insertBefore(frag, anchor);
|
|
@@ -21359,7 +21568,7 @@ code-viewer query clear --db app.db`
|
|
|
21359
21568
|
li.dataset.path = f2.path;
|
|
21360
21569
|
li.dataset.type = "blob";
|
|
21361
21570
|
li.classList.toggle("viewed", !onFileClick && STATE.viewedFiles.has(f2.path));
|
|
21362
|
-
li.classList.toggle("hidden-by-tests", STATE.hideTests && isTestPath(f2.path || ""));
|
|
21571
|
+
li.classList.toggle("hidden-by-tests", STATE.hideTests && !isRepositorySidebarMode() && isTestPath(f2.path || ""));
|
|
21363
21572
|
li.style.setProperty("--lvl-pad", `${12 + depth * 14}px`);
|
|
21364
21573
|
const spacer = document.createElement("span");
|
|
21365
21574
|
spacer.className = "chev-spacer";
|
|
@@ -21458,7 +21667,7 @@ code-viewer query clear --db app.db`
|
|
|
21458
21667
|
}
|
|
21459
21668
|
subtreeVisible = subtreeVisible || visible;
|
|
21460
21669
|
} else {
|
|
21461
|
-
const testHidden = STATE.hideTests && isTestPath(item.file.path || "");
|
|
21670
|
+
const testHidden = STATE.hideTests && !isRepositorySidebarMode() && isTestPath(item.file.path || "");
|
|
21462
21671
|
const visible = !testHidden && matches(item.file.path || "");
|
|
21463
21672
|
if (visible) {
|
|
21464
21673
|
rows.push({
|
|
@@ -25460,6 +25669,9 @@ code-viewer query clear --db app.db`
|
|
|
25460
25669
|
let highlightLoadPromise = null;
|
|
25461
25670
|
let SERVER_SCOPE_OMIT_DIRS_DEFAULT = [];
|
|
25462
25671
|
let SERVER_SCOPE_EXCLUDE_NAMES_DEFAULT = [];
|
|
25672
|
+
let SERVER_SCOPE_WATCH_LIMIT_DEFAULT = 1024;
|
|
25673
|
+
let SERVER_SCOPE_WATCH_LIMIT_MIN = 16;
|
|
25674
|
+
let SERVER_SCOPE_WATCH_LIMIT_MAX = 65536;
|
|
25463
25675
|
const UNDO_STACK = [];
|
|
25464
25676
|
let PENDING_G_SCOPE = null;
|
|
25465
25677
|
let PENDING_G_UNTIL = 0;
|
|
@@ -25826,6 +26038,12 @@ code-viewer query clear --db app.db`
|
|
|
25826
26038
|
}
|
|
25827
26039
|
SERVER_SCOPE_OMIT_DIRS_DEFAULT = normalizeScopeOmitDirs(settings.scope.omit_dirs_effective);
|
|
25828
26040
|
SERVER_SCOPE_EXCLUDE_NAMES_DEFAULT = normalizeScopeExcludeNames(settings.scope.exclude_names_effective);
|
|
26041
|
+
if (typeof settings.scope.watch_limit_default === "number")
|
|
26042
|
+
SERVER_SCOPE_WATCH_LIMIT_DEFAULT = settings.scope.watch_limit_default;
|
|
26043
|
+
if (typeof settings.scope.watch_limit_min === "number")
|
|
26044
|
+
SERVER_SCOPE_WATCH_LIMIT_MIN = settings.scope.watch_limit_min;
|
|
26045
|
+
if (typeof settings.scope.watch_limit_max === "number")
|
|
26046
|
+
SERVER_SCOPE_WATCH_LIMIT_MAX = settings.scope.watch_limit_max;
|
|
25829
26047
|
return settings;
|
|
25830
26048
|
} catch {
|
|
25831
26049
|
return null;
|
|
@@ -26184,15 +26402,20 @@ code-viewer query clear --db app.db`
|
|
|
26184
26402
|
displaySource: "Applies to all projects in this browser.",
|
|
26185
26403
|
excludedDirectories: "Excluded directories",
|
|
26186
26404
|
omitDirs: "Skip these directory names while browsing and searching",
|
|
26405
|
+
omitDirsHelp: "Reads no contents inside these directories. Applies to the sidebar (Files), Ctrl+K (file search), Ctrl+G (grep), Datastores, and the file change watcher.",
|
|
26187
26406
|
excludeNames: "Hide these file or directory names completely",
|
|
26407
|
+
excludeNamesHelp: "Removes matching files or directories from the sidebar, search, and grep results entirely. Unlike Skip, the names themselves disappear from the UI.",
|
|
26188
26408
|
reset: "Restore defaults",
|
|
26189
26409
|
autosaveNote: "Changes save automatically.",
|
|
26190
|
-
scopeSource: (project, source) => `Saved for project "${project}" in this browser. Source: ${source}. Used by
|
|
26410
|
+
scopeSource: (project, source) => `Saved for project "${project}" in this browser. Source: ${source}. Used by the sidebar, Ctrl+K, Ctrl+G, Datastores, and the file change watcher. Restore defaults removes the browser override.`,
|
|
26191
26411
|
browserOverride: "Browser override",
|
|
26192
26412
|
serverDefault: "Server default",
|
|
26193
26413
|
uploadsTitle: "Uploads",
|
|
26194
26414
|
uploadEnabledLabel: "Allow file uploads into worktree folders",
|
|
26195
|
-
uploadEnabledHelp: "Disable to make the worktree read-only for everyone using this server."
|
|
26415
|
+
uploadEnabledHelp: "Disable to make the worktree read-only for everyone using this server.",
|
|
26416
|
+
watchTitle: "File change watcher",
|
|
26417
|
+
watchLimit: "Maximum directories to watch",
|
|
26418
|
+
watchLimitHelp: (defaultLimit) => `Higher values reduce missed updates in deep trees at the cost of file handles. Combine with the Skip list above to keep heavy folders (node_modules, .git, dist...) out of the watch budget. Default: ${defaultLimit}.`
|
|
26196
26419
|
},
|
|
26197
26420
|
annotations: {
|
|
26198
26421
|
title: "Code annotations",
|
|
@@ -26280,15 +26503,20 @@ code-viewer query clear --db app.db`
|
|
|
26280
26503
|
displaySource: "このブラウザのすべてのプロジェクトに適用されます。",
|
|
26281
26504
|
excludedDirectories: "除外ディレクトリ",
|
|
26282
26505
|
omitDirs: "閲覧と検索でスキップするディレクトリ名",
|
|
26506
|
+
omitDirsHelp: "これらのディレクトリの中身は読み込みません。サイドバー(Files)・Ctrl+K(ファイル検索)・Ctrl+G(grep)・Datastores・File change watcher の5機能すべてに適用されます。",
|
|
26283
26507
|
excludeNames: "完全に非表示にするファイル名またはディレクトリ名",
|
|
26508
|
+
excludeNamesHelp: "リスト中の名前に一致するファイル/ディレクトリを、サイドバー・検索結果・grep 結果から完全に消します。Skip と違い、名前自体が UI に出なくなります。",
|
|
26284
26509
|
reset: "デフォルトに戻す",
|
|
26285
26510
|
autosaveNote: "変更は自動で保存されます。",
|
|
26286
|
-
scopeSource: (project, source) => `このブラウザのプロジェクト "${project}" に保存されます。ソース: ${source}
|
|
26511
|
+
scopeSource: (project, source) => `このブラウザのプロジェクト "${project}" に保存されます。ソース: ${source}。サイドバー、Ctrl+K、Ctrl+G、Datastores、File change watcher で使われます。「デフォルトに戻す」でブラウザ側の上書きを削除します。`,
|
|
26287
26512
|
browserOverride: "ブラウザ側の上書き",
|
|
26288
26513
|
serverDefault: "サーバ既定値",
|
|
26289
26514
|
uploadsTitle: "アップロード",
|
|
26290
26515
|
uploadEnabledLabel: "ワークツリーへのファイルアップロードを許可する",
|
|
26291
|
-
uploadEnabledHelp: "オフにすると、このサーバを使う全員に対してワークツリーは読み取り専用になります。"
|
|
26516
|
+
uploadEnabledHelp: "オフにすると、このサーバを使う全員に対してワークツリーは読み取り専用になります。",
|
|
26517
|
+
watchTitle: "ファイル変更の監視",
|
|
26518
|
+
watchLimit: "監視するディレクトリ数の上限",
|
|
26519
|
+
watchLimitHelp: (defaultLimit) => `値を大きくすると深いツリーの変更を取りこぼしにくくなりますが、ファイルハンドル数を消費します。上の Skip リストと併用すると、重いフォルダ(node_modules, .git, dist など)を監視枠から外せます。既定値: ${defaultLimit}。`
|
|
26292
26520
|
},
|
|
26293
26521
|
annotations: {
|
|
26294
26522
|
title: "コード注釈",
|
|
@@ -26406,14 +26634,20 @@ code-viewer query clear --db app.db`
|
|
|
26406
26634
|
settingsSections[1].textContent = text2.settings.uploadsTitle;
|
|
26407
26635
|
if (settingsSections[2])
|
|
26408
26636
|
settingsSections[2].textContent = text2.settings.excludedDirectories;
|
|
26637
|
+
if (settingsSections[3])
|
|
26638
|
+
settingsSections[3].textContent = text2.settings.watchTitle;
|
|
26409
26639
|
setElementText("#upload-enabled-label", text2.settings.uploadEnabledLabel);
|
|
26410
26640
|
setElementText("#upload-help", text2.settings.uploadEnabledHelp);
|
|
26641
|
+
setElementText("#scope-omit-dirs-help", text2.settings.omitDirsHelp);
|
|
26642
|
+
setElementText("#scope-exclude-names-help", text2.settings.excludeNamesHelp);
|
|
26643
|
+
setElementText("#scope-watch-limit-help", text2.settings.watchLimitHelp(SERVER_SCOPE_WATCH_LIMIT_DEFAULT));
|
|
26411
26644
|
const labelMap = {
|
|
26412
26645
|
"viewer-language": text2.settings.language,
|
|
26413
26646
|
"sidebar-font-size": text2.settings.fileListFontSize,
|
|
26414
26647
|
"code-font-size": text2.settings.codeFontSize,
|
|
26415
26648
|
"scope-omit-dirs": text2.settings.omitDirs,
|
|
26416
|
-
"scope-exclude-names": text2.settings.excludeNames
|
|
26649
|
+
"scope-exclude-names": text2.settings.excludeNames,
|
|
26650
|
+
"scope-watch-limit": text2.settings.watchLimit
|
|
26417
26651
|
};
|
|
26418
26652
|
Object.entries(labelMap).forEach(([id, label]) => {
|
|
26419
26653
|
const labelEl = document.querySelector(`label[for="${id}"]`);
|
|
@@ -26597,6 +26831,19 @@ code-viewer query clear --db app.db`
|
|
|
26597
26831
|
`);
|
|
26598
26832
|
excludeInput.value = effectiveScopeExcludeNames().join(`
|
|
26599
26833
|
`);
|
|
26834
|
+
const watchLimitInput = document.querySelector("#scope-watch-limit");
|
|
26835
|
+
const watchLimitRange = document.querySelector("#scope-watch-limit-range");
|
|
26836
|
+
const watchLimitValue = effectiveScopeWatchLimit();
|
|
26837
|
+
if (watchLimitInput) {
|
|
26838
|
+
watchLimitInput.min = String(SERVER_SCOPE_WATCH_LIMIT_MIN);
|
|
26839
|
+
watchLimitInput.max = String(SERVER_SCOPE_WATCH_LIMIT_MAX);
|
|
26840
|
+
watchLimitInput.value = String(watchLimitValue);
|
|
26841
|
+
}
|
|
26842
|
+
if (watchLimitRange) {
|
|
26843
|
+
watchLimitRange.min = String(SERVER_SCOPE_WATCH_LIMIT_MIN);
|
|
26844
|
+
watchLimitRange.max = String(SERVER_SCOPE_WATCH_LIMIT_MAX);
|
|
26845
|
+
watchLimitRange.value = String(watchLimitValue);
|
|
26846
|
+
}
|
|
26600
26847
|
const uploadToggle = document.querySelector("#upload-enabled");
|
|
26601
26848
|
if (uploadToggle)
|
|
26602
26849
|
uploadToggle.checked = APP_SETTINGS.uploadEnabled !== false;
|
|
@@ -26653,6 +26900,41 @@ code-viewer query clear --db app.db`
|
|
|
26653
26900
|
refreshScopeSourceLabel();
|
|
26654
26901
|
refreshRepositoryTreeAfterSettings();
|
|
26655
26902
|
}
|
|
26903
|
+
function normalizeScopeWatchLimit(value) {
|
|
26904
|
+
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
26905
|
+
if (!Number.isFinite(parsed))
|
|
26906
|
+
return null;
|
|
26907
|
+
const floored = Math.floor(parsed);
|
|
26908
|
+
if (floored < SERVER_SCOPE_WATCH_LIMIT_MIN)
|
|
26909
|
+
return SERVER_SCOPE_WATCH_LIMIT_MIN;
|
|
26910
|
+
if (floored > SERVER_SCOPE_WATCH_LIMIT_MAX)
|
|
26911
|
+
return SERVER_SCOPE_WATCH_LIMIT_MAX;
|
|
26912
|
+
return floored;
|
|
26913
|
+
}
|
|
26914
|
+
function effectiveScopeWatchLimit() {
|
|
26915
|
+
const saved = normalizeScopeWatchLimit(APP_SETTINGS.scopeWatchLimit);
|
|
26916
|
+
return saved ?? SERVER_SCOPE_WATCH_LIMIT_DEFAULT;
|
|
26917
|
+
}
|
|
26918
|
+
function syncScopeWatchLimitInputs(value) {
|
|
26919
|
+
const numberInput = document.querySelector("#scope-watch-limit");
|
|
26920
|
+
const rangeInput = document.querySelector("#scope-watch-limit-range");
|
|
26921
|
+
if (numberInput && numberInput.value !== String(value))
|
|
26922
|
+
numberInput.value = String(value);
|
|
26923
|
+
if (rangeInput && rangeInput.value !== String(value))
|
|
26924
|
+
rangeInput.value = String(value);
|
|
26925
|
+
}
|
|
26926
|
+
function saveScopeWatchLimitField(value) {
|
|
26927
|
+
const next = normalizeScopeWatchLimit(value);
|
|
26928
|
+
const resolved = next ?? SERVER_SCOPE_WATCH_LIMIT_DEFAULT;
|
|
26929
|
+
mergeLocalSettings({ scopeWatchLimit: resolved });
|
|
26930
|
+
patchSettings({ scopeWatchLimit: resolved });
|
|
26931
|
+
syncScopeWatchLimitInputs(resolved);
|
|
26932
|
+
}
|
|
26933
|
+
function previewScopeWatchLimit(value) {
|
|
26934
|
+
const next = normalizeScopeWatchLimit(value);
|
|
26935
|
+
if (next != null)
|
|
26936
|
+
syncScopeWatchLimitInputs(next);
|
|
26937
|
+
}
|
|
26656
26938
|
function resetScopeSettings() {
|
|
26657
26939
|
setViewerLanguage("en", false);
|
|
26658
26940
|
mergeLocalSettings({
|
|
@@ -26660,6 +26942,7 @@ code-viewer query clear --db app.db`
|
|
|
26660
26942
|
codeFontSize: null,
|
|
26661
26943
|
scopeOmitDirs: null,
|
|
26662
26944
|
scopeExcludeNames: null,
|
|
26945
|
+
scopeWatchLimit: null,
|
|
26663
26946
|
uploadEnabled: null
|
|
26664
26947
|
});
|
|
26665
26948
|
applySidebarFontSize("regular");
|
|
@@ -26670,6 +26953,7 @@ code-viewer query clear --db app.db`
|
|
|
26670
26953
|
codeFontSize: null,
|
|
26671
26954
|
scopeOmitDirs: null,
|
|
26672
26955
|
scopeExcludeNames: null,
|
|
26956
|
+
scopeWatchLimit: null,
|
|
26673
26957
|
uploadEnabled: null
|
|
26674
26958
|
});
|
|
26675
26959
|
const sidebarFontSize = document.querySelector("#sidebar-font-size");
|
|
@@ -26848,6 +27132,7 @@ code-viewer query clear --db app.db`
|
|
|
26848
27132
|
if (STATE.route.screen !== "database") {
|
|
26849
27133
|
setQueryHistoryPanelOpen(false);
|
|
26850
27134
|
}
|
|
27135
|
+
applyHideTests();
|
|
26851
27136
|
}
|
|
26852
27137
|
function syncHeaderMenu() {
|
|
26853
27138
|
document.querySelectorAll(".app-menu-item, .global-help-link").forEach((link2) => {
|
|
@@ -27087,6 +27372,15 @@ code-viewer query clear --db app.db`
|
|
|
27087
27372
|
$("#scope-exclude-names")?.addEventListener("change", (event) => {
|
|
27088
27373
|
saveScopeExcludeNamesField(event.currentTarget.value);
|
|
27089
27374
|
});
|
|
27375
|
+
$("#scope-watch-limit")?.addEventListener("change", (event) => {
|
|
27376
|
+
saveScopeWatchLimitField(event.currentTarget.value);
|
|
27377
|
+
});
|
|
27378
|
+
$("#scope-watch-limit-range")?.addEventListener("input", (event) => {
|
|
27379
|
+
previewScopeWatchLimit(event.currentTarget.value);
|
|
27380
|
+
});
|
|
27381
|
+
$("#scope-watch-limit-range")?.addEventListener("change", (event) => {
|
|
27382
|
+
saveScopeWatchLimitField(event.currentTarget.value);
|
|
27383
|
+
});
|
|
27090
27384
|
$("#scope-settings-popover")?.addEventListener("keydown", (e2) => {
|
|
27091
27385
|
if (isImeComposing(e2))
|
|
27092
27386
|
return;
|
|
@@ -27740,13 +28034,14 @@ code-viewer query clear --db app.db`
|
|
|
27740
28034
|
const btn = $("#hide-tests");
|
|
27741
28035
|
if (btn)
|
|
27742
28036
|
btn.classList.toggle("active", STATE.hideTests);
|
|
28037
|
+
const effective = STATE.hideTests && !isRepositorySidebarMode();
|
|
27743
28038
|
document.querySelectorAll(".gdp-file-shell").forEach((card) => {
|
|
27744
28039
|
const isTest = TEST_RE.test(card.dataset.path || "");
|
|
27745
|
-
card.classList.toggle("hidden-by-tests",
|
|
28040
|
+
card.classList.toggle("hidden-by-tests", effective && isTest);
|
|
27746
28041
|
});
|
|
27747
28042
|
document.querySelectorAll("#filelist li[data-path]").forEach((li) => {
|
|
27748
28043
|
const isTest = TEST_RE.test(li.dataset.path || "");
|
|
27749
|
-
li.classList.toggle("hidden-by-tests",
|
|
28044
|
+
li.classList.toggle("hidden-by-tests", effective && isTest);
|
|
27750
28045
|
});
|
|
27751
28046
|
if (isVirtualSidebarActive())
|
|
27752
28047
|
rerenderVirtualSidebar();
|
package/web/index.html
CHANGED
|
@@ -177,10 +177,21 @@
|
|
|
177
177
|
<strong class="scope-settings-section-title">Excluded directories</strong>
|
|
178
178
|
<label for="scope-omit-dirs">Skip these directory names while browsing and searching</label>
|
|
179
179
|
<textarea id="scope-omit-dirs" rows="6" spellcheck="false"></textarea>
|
|
180
|
+
<p id="scope-omit-dirs-help" class="scope-settings-help"></p>
|
|
180
181
|
<label for="scope-exclude-names">Hide these file or directory names completely</label>
|
|
181
182
|
<textarea id="scope-exclude-names" rows="4" spellcheck="false"></textarea>
|
|
183
|
+
<p id="scope-exclude-names-help" class="scope-settings-help"></p>
|
|
182
184
|
<p id="scope-omit-source"></p>
|
|
183
185
|
</div>
|
|
186
|
+
<div class="scope-settings-section">
|
|
187
|
+
<strong id="watch-section-title" class="scope-settings-section-title">File change watcher</strong>
|
|
188
|
+
<label for="scope-watch-limit">Maximum directories to watch</label>
|
|
189
|
+
<div class="scope-watch-limit-row">
|
|
190
|
+
<input id="scope-watch-limit-range" type="range" min="16" max="65536" step="16" aria-label="watch limit slider" />
|
|
191
|
+
<input id="scope-watch-limit" type="number" min="16" max="65536" step="1" />
|
|
192
|
+
</div>
|
|
193
|
+
<p id="scope-watch-limit-help" class="scope-settings-help"></p>
|
|
194
|
+
</div>
|
|
184
195
|
<div class="scope-settings-footer">
|
|
185
196
|
<p id="scope-settings-autosave-note" class="scope-settings-help"></p>
|
|
186
197
|
<button id="scope-omit-reset" type="button">Restore defaults</button>
|
package/web/style.css
CHANGED
|
@@ -1735,6 +1735,20 @@ body.gdp-history-resizing * {
|
|
|
1735
1735
|
gap: 8px;
|
|
1736
1736
|
margin-top: 10px;
|
|
1737
1737
|
}
|
|
1738
|
+
.scope-watch-limit-row {
|
|
1739
|
+
display: flex;
|
|
1740
|
+
align-items: center;
|
|
1741
|
+
gap: 10px;
|
|
1742
|
+
}
|
|
1743
|
+
.scope-watch-limit-row input[type="range"] {
|
|
1744
|
+
flex: 1;
|
|
1745
|
+
min-width: 0;
|
|
1746
|
+
accent-color: var(--accent, #2b6cb0);
|
|
1747
|
+
}
|
|
1748
|
+
.scope-watch-limit-row input[type="number"] {
|
|
1749
|
+
width: 88px;
|
|
1750
|
+
flex: 0 0 auto;
|
|
1751
|
+
}
|
|
1738
1752
|
.scope-settings-toggle {
|
|
1739
1753
|
display: flex;
|
|
1740
1754
|
align-items: center;
|