@youtyan/code-viewer 0.2.8 → 0.2.9

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.
@@ -3726,286 +3726,10 @@ var init_search = __esm(() => {
3726
3726
  DEFAULT_EXCLUDE_NAMES = [".DS_Store"];
3727
3727
  });
3728
3728
 
3729
- // web-src/server/worktree-watcher.ts
3730
- import {
3731
- lstatSync as lstatSync3,
3732
- readdirSync as nodeReaddirSync,
3733
- watch as nodeWatch
3734
- } from "node:fs";
3735
- import { join as join7, relative } from "node:path";
3736
- function normalizeRelativePath(path) {
3737
- return path.replace(/\\/g, "/").replace(/^\/+/, "");
3738
- }
3739
- function isInsideRoot(root, path) {
3740
- const rel = relative(root, path).replace(/\\/g, "/");
3741
- return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
3742
- }
3743
- function startWorktreeUpdateWatch(options) {
3744
- const watch = options.watch || nodeWatch;
3745
- const readDirs = options.readdirSync || ((path) => nodeReaddirSync(path, { withFileTypes: true }));
3746
- const isDirectory = options.isDirectory || ((path) => {
3747
- try {
3748
- return lstatSync3(path).isDirectory();
3749
- } catch {
3750
- return false;
3751
- }
3752
- });
3753
- const directorySignature = options.directorySignature || ((path) => {
3754
- try {
3755
- const stats = lstatSync3(path);
3756
- if (!stats.isDirectory())
3757
- return null;
3758
- return `${stats.dev}:${stats.ino}`;
3759
- } catch {
3760
- return null;
3761
- }
3762
- });
3763
- const setTimer = options.setTimeoutFn || setTimeout;
3764
- const clearTimer = options.clearTimeoutFn || clearTimeout;
3765
- const debounceMs = options.debounceMs ?? 250;
3766
- const maxWatchedDirectories = Math.max(1, Math.floor(options.maxWatchedDirectories ?? DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT));
3767
- const watchers = new Map;
3768
- const signatures = new Map;
3769
- const initialScanAsync = options.initialScanMode === "async" || (!options.watch || options.watch === nodeWatch) && !options.readdirSync;
3770
- const initialScanQueue = [];
3771
- let initialScanTimer = null;
3772
- const pendingPathInspections = new Map;
3773
- let pathInspectionTimer = null;
3774
- let timer = null;
3775
- const pendingChangedPaths = new Set;
3776
- let watchLimitReported = false;
3777
- const ignored = (path) => isSkippableSearchPath(normalizeRelativePath(path), options.omitDirNames, options.excludeNames);
3778
- const directoryRelativePath = (dir) => normalizeRelativePath(relative(options.root, dir));
3779
- const ignoredDirectory = (dir) => {
3780
- const rel = directoryRelativePath(dir);
3781
- return Boolean(rel && ignored(rel));
3782
- };
3783
- const scheduleUpdate = (changedPath) => {
3784
- if (changedPath)
3785
- pendingChangedPaths.add(changedPath);
3786
- if (timer)
3787
- clearTimer(timer);
3788
- timer = setTimer(() => {
3789
- timer = null;
3790
- const paths = pendingChangedPaths.size ? [...pendingChangedPaths] : undefined;
3791
- pendingChangedPaths.clear();
3792
- options.onUpdate(paths);
3793
- }, debounceMs);
3794
- };
3795
- const reportWatchLimit = () => {
3796
- if (watchLimitReported)
3797
- return;
3798
- watchLimitReported = true;
3799
- options.onError?.(new Error(`worktree watcher cap reached (${maxWatchedDirectories}); subsequent changes may be missed`));
3800
- };
3801
- const closeSubtree = (dir) => {
3802
- for (const [watchedDir, watcher] of [...watchers]) {
3803
- if (watchedDir !== dir && !watchedDir.startsWith(`${dir}/`))
3804
- continue;
3805
- try {
3806
- watcher.close?.();
3807
- } catch {}
3808
- watchers.delete(watchedDir);
3809
- signatures.delete(watchedDir);
3810
- }
3811
- };
3812
- const closeAll = () => {
3813
- if (initialScanTimer) {
3814
- clearTimer(initialScanTimer);
3815
- initialScanTimer = null;
3816
- }
3817
- if (pathInspectionTimer) {
3818
- clearTimer(pathInspectionTimer);
3819
- pathInspectionTimer = null;
3820
- }
3821
- initialScanQueue.length = 0;
3822
- pendingPathInspections.clear();
3823
- for (const watcher of [...watchers.values()]) {
3824
- try {
3825
- watcher.close?.();
3826
- } catch {}
3827
- }
3828
- watchers.clear();
3829
- signatures.clear();
3830
- };
3831
- const readChildDirectories = (dir) => {
3832
- let entries;
3833
- try {
3834
- entries = readDirs(dir);
3835
- } catch (error) {
3836
- options.onError?.(error);
3837
- return [];
3838
- }
3839
- const children = [];
3840
- for (const entry of entries) {
3841
- if (!entry.isDirectory())
3842
- continue;
3843
- const child = join7(dir, entry.name);
3844
- if (ignoredDirectory(child))
3845
- continue;
3846
- children.push(child);
3847
- }
3848
- return children;
3849
- };
3850
- const processInitialScanQueue = () => {
3851
- initialScanTimer = null;
3852
- if (watchers.size >= maxWatchedDirectories) {
3853
- reportWatchLimit();
3854
- initialScanQueue.length = 0;
3855
- return;
3856
- }
3857
- const next = initialScanQueue.shift();
3858
- if (next)
3859
- watchDirectory(next, true);
3860
- if (watchers.size >= maxWatchedDirectories) {
3861
- reportWatchLimit();
3862
- initialScanQueue.length = 0;
3863
- }
3864
- if (initialScanQueue.length)
3865
- initialScanTimer = setTimer(processInitialScanQueue, 50);
3866
- };
3867
- const queueInitialChildren = (dir) => {
3868
- const remaining = maxWatchedDirectories - watchers.size;
3869
- if (remaining <= 0) {
3870
- reportWatchLimit();
3871
- return;
3872
- }
3873
- const children = readChildDirectories(dir);
3874
- if (children.length > remaining)
3875
- reportWatchLimit();
3876
- initialScanQueue.push(...children.slice(0, remaining));
3877
- if (!initialScanTimer)
3878
- initialScanTimer = setTimer(processInitialScanQueue, 5000);
3879
- };
3880
- const processChangedPath = (changed, fullChangedPath) => {
3881
- const known = watchers.has(fullChangedPath);
3882
- if (isDirectory(fullChangedPath)) {
3883
- if (known) {
3884
- const signature = directorySignature(fullChangedPath);
3885
- if (signature && signature !== signatures.get(fullChangedPath)) {
3886
- closeSubtree(fullChangedPath);
3887
- watchDirectory(fullChangedPath, initialScanAsync);
3888
- }
3889
- scheduleUpdate(changed);
3890
- return;
3891
- }
3892
- watchDirectory(fullChangedPath, initialScanAsync);
3893
- } else if (known) {
3894
- closeSubtree(fullChangedPath);
3895
- }
3896
- scheduleUpdate(changed);
3897
- };
3898
- const processPathInspections = () => {
3899
- pathInspectionTimer = null;
3900
- const entries = [...pendingPathInspections];
3901
- pendingPathInspections.clear();
3902
- for (const [changed, fullChangedPath] of entries) {
3903
- processChangedPath(changed, fullChangedPath);
3904
- }
3905
- };
3906
- const queuePathInspection = (changed, fullChangedPath) => {
3907
- pendingPathInspections.set(changed, fullChangedPath);
3908
- if (!pathInspectionTimer)
3909
- pathInspectionTimer = setTimer(processPathInspections, 25);
3910
- };
3911
- const watchDirectory = (dir, initialScan = false) => {
3912
- if (watchers.has(dir))
3913
- return;
3914
- if (watchers.size >= maxWatchedDirectories) {
3915
- reportWatchLimit();
3916
- return;
3917
- }
3918
- const rel = directoryRelativePath(dir);
3919
- if (rel && ignored(rel))
3920
- return;
3921
- try {
3922
- const watcher = watch(dir, { persistent: false }, (_event, filename) => {
3923
- if (!filename) {
3924
- scheduleUpdate();
3925
- return;
3926
- }
3927
- const changed = normalizeRelativePath(join7(rel, filename.toString()));
3928
- if (ignored(changed))
3929
- return;
3930
- const fullChangedPath = join7(options.root, changed);
3931
- if (!isInsideRoot(options.root, fullChangedPath))
3932
- return;
3933
- if (initialScanAsync) {
3934
- queuePathInspection(changed, fullChangedPath);
3935
- return;
3936
- }
3937
- processChangedPath(changed, fullChangedPath);
3938
- }) || {};
3939
- watchers.set(dir, watcher);
3940
- const signature = directorySignature(dir);
3941
- if (signature)
3942
- signatures.set(dir, signature);
3943
- watcher.on?.("error", () => {
3944
- if (watchers.get(dir) === watcher) {
3945
- watchers.delete(dir);
3946
- signatures.delete(dir);
3947
- }
3948
- });
3949
- watcher.on?.("close", () => {
3950
- if (watchers.get(dir) === watcher) {
3951
- watchers.delete(dir);
3952
- signatures.delete(dir);
3953
- }
3954
- });
3955
- } catch (error) {
3956
- options.onError?.(error);
3957
- return;
3958
- }
3959
- if (initialScanAsync && initialScan) {
3960
- queueInitialChildren(dir);
3961
- return;
3962
- }
3963
- if (watchers.size >= maxWatchedDirectories) {
3964
- reportWatchLimit();
3965
- return;
3966
- }
3967
- for (const child of readChildDirectories(dir))
3968
- watchDirectory(child);
3969
- };
3970
- watchDirectory(options.root, true);
3971
- return { started: watchers.size > 0, close: closeAll };
3972
- }
3973
- var DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT = 256;
3974
- var init_worktree_watcher = __esm(() => {
3975
- init_search();
3976
- });
3977
-
3978
- // web-src/core/control-chars.ts
3979
- function hasControlCharacter(value) {
3980
- for (const ch of value) {
3981
- const code = ch.charCodeAt(0);
3982
- if (code < 32 || code === 127)
3983
- return true;
3984
- }
3985
- return false;
3986
- }
3987
-
3988
- // web-src/core/id.ts
3989
- function bytesToHex(bytes) {
3990
- return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
3991
- }
3992
- function makeId(prefix) {
3993
- const cryptoApi = globalThis.crypto;
3994
- if (typeof cryptoApi?.randomUUID === "function") {
3995
- return `${prefix}-${cryptoApi.randomUUID().replace(/-/g, "").slice(0, 16)}`;
3996
- }
3997
- if (typeof cryptoApi?.getRandomValues === "function") {
3998
- const bytes = new Uint8Array(8);
3999
- cryptoApi.getRandomValues(bytes);
4000
- return `${prefix}-${bytesToHex(bytes)}`;
4001
- }
4002
- return `${prefix}-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
4003
- }
4004
-
4005
3729
  // web-src/server/state-store.ts
4006
- import { join as join8 } from "node:path";
3730
+ import { join as join7 } from "node:path";
4007
3731
  function codeViewerPath(root, fileName) {
4008
- return join8(root, CODE_VIEWER_DIR2, fileName);
3732
+ return join7(root, CODE_VIEWER_DIR2, fileName);
4009
3733
  }
4010
3734
  function isRecord(value) {
4011
3735
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -4065,7 +3789,12 @@ function emptySettings() {
4065
3789
  return { version: 1 };
4066
3790
  }
4067
3791
  function emptyViewState() {
4068
- return { version: 1, collapsedDirs: [], viewedFiles: [] };
3792
+ return {
3793
+ version: 1,
3794
+ collapsedDirs: [],
3795
+ lazyExpandedDirs: [],
3796
+ viewedFiles: []
3797
+ };
4069
3798
  }
4070
3799
  function emptyDbUiState() {
4071
3800
  return { version: 1, columnWidths: {} };
@@ -4138,6 +3867,9 @@ function sanitizeSettings(raw) {
4138
3867
  });
4139
3868
  if (scopeExcludeNames)
4140
3869
  out.scopeExcludeNames = scopeExcludeNames;
3870
+ const uploadEnabled = optionalBoolean(raw.uploadEnabled);
3871
+ if (uploadEnabled !== undefined)
3872
+ out.uploadEnabled = uploadEnabled;
4141
3873
  if (isRecord(raw.range)) {
4142
3874
  const from = optionalString(raw.range.from, MAX_REF_LEN);
4143
3875
  const to = optionalString(raw.range.to, MAX_REF_LEN);
@@ -4171,6 +3903,12 @@ function sanitizeViewState(raw) {
4171
3903
  keepLast: true,
4172
3904
  sort: false
4173
3905
  }) ?? [],
3906
+ lazyExpandedDirs: normalizeStringList(raw.lazyExpandedDirs, {
3907
+ maxItems: MAX_VIEW_ITEMS,
3908
+ maxLen: MAX_KEY_LEN,
3909
+ keepLast: true,
3910
+ sort: false
3911
+ }) ?? [],
4174
3912
  viewedFiles: normalizeStringList(raw.viewedFiles, {
4175
3913
  maxItems: MAX_VIEW_ITEMS,
4176
3914
  maxLen: MAX_KEY_LEN,
@@ -4184,6 +3922,7 @@ function mergeViewState(current, patch) {
4184
3922
  return current;
4185
3923
  const base = sanitizeViewState({ ...current, version: 1 });
4186
3924
  const collapsedDirs = new Set(base.collapsedDirs);
3925
+ const lazyExpandedDirs = new Set(base.lazyExpandedDirs);
4187
3926
  const viewedFiles = new Set(base.viewedFiles);
4188
3927
  const addedCollapsedDirs = normalizeStringList(patch.addedCollapsedDirs, {
4189
3928
  maxItems: MAX_VIEW_ITEMS,
@@ -4191,8 +3930,10 @@ function mergeViewState(current, patch) {
4191
3930
  keepLast: true,
4192
3931
  sort: false
4193
3932
  });
4194
- for (const path of addedCollapsedDirs || [])
3933
+ for (const path of addedCollapsedDirs || []) {
4195
3934
  collapsedDirs.add(path);
3935
+ lazyExpandedDirs.delete(path);
3936
+ }
4196
3937
  const removedCollapsedDirs = normalizeStringList(patch.removedCollapsedDirs, {
4197
3938
  maxItems: MAX_VIEW_ITEMS,
4198
3939
  maxLen: MAX_KEY_LEN,
@@ -4200,6 +3941,23 @@ function mergeViewState(current, patch) {
4200
3941
  });
4201
3942
  for (const path of removedCollapsedDirs || [])
4202
3943
  collapsedDirs.delete(path);
3944
+ const addedLazyExpandedDirs = normalizeStringList(patch.addedLazyExpandedDirs, {
3945
+ maxItems: MAX_VIEW_ITEMS,
3946
+ maxLen: MAX_KEY_LEN,
3947
+ keepLast: true,
3948
+ sort: false
3949
+ });
3950
+ for (const path of addedLazyExpandedDirs || []) {
3951
+ if (!collapsedDirs.has(path))
3952
+ lazyExpandedDirs.add(path);
3953
+ }
3954
+ const removedLazyExpandedDirs = normalizeStringList(patch.removedLazyExpandedDirs, {
3955
+ maxItems: MAX_VIEW_ITEMS,
3956
+ maxLen: MAX_KEY_LEN,
3957
+ sort: false
3958
+ });
3959
+ for (const path of removedLazyExpandedDirs || [])
3960
+ lazyExpandedDirs.delete(path);
4203
3961
  const addedViewedFiles = normalizeStringList(patch.addedViewedFiles, {
4204
3962
  maxItems: MAX_VIEW_ITEMS,
4205
3963
  maxLen: MAX_KEY_LEN,
@@ -4218,6 +3976,7 @@ function mergeViewState(current, patch) {
4218
3976
  return sanitizeViewState({
4219
3977
  version: 1,
4220
3978
  collapsedDirs: [...collapsedDirs],
3979
+ lazyExpandedDirs: [...lazyExpandedDirs],
4221
3980
  viewedFiles: [...viewedFiles]
4222
3981
  });
4223
3982
  }
@@ -4226,9 +3985,24 @@ function safeObjectKey(value) {
4226
3985
  return null;
4227
3986
  return value;
4228
3987
  }
3988
+ function sanitizeDbUiPrefs(raw) {
3989
+ if (!isRecord(raw))
3990
+ return;
3991
+ const out = {};
3992
+ for (const key of DB_UI_BOOL_PREF_KEYS) {
3993
+ const v = raw[key];
3994
+ if (v === true || v === false)
3995
+ out[key] = v;
3996
+ }
3997
+ return Object.keys(out).length > 0 ? out : undefined;
3998
+ }
4229
3999
  function sanitizeDbUiState(raw) {
4230
- if (!isRecord(raw) || !isRecord(raw.columnWidths))
4000
+ if (!isRecord(raw))
4231
4001
  return emptyDbUiState();
4002
+ const prefs = sanitizeDbUiPrefs(raw.prefs);
4003
+ if (!isRecord(raw.columnWidths)) {
4004
+ return prefs ? { ...emptyDbUiState(), prefs } : emptyDbUiState();
4005
+ }
4232
4006
  const columnWidths = {};
4233
4007
  let dbCount = 0;
4234
4008
  for (const [dbIdRaw, tablesRaw] of Object.entries(raw.columnWidths)) {
@@ -4267,100 +4041,406 @@ function sanitizeDbUiState(raw) {
4267
4041
  columnWidths[dbId] = tables;
4268
4042
  dbCount++;
4269
4043
  }
4270
- return { version: 1, columnWidths };
4044
+ const out = { version: 1, columnWidths };
4045
+ if (prefs)
4046
+ out.prefs = prefs;
4047
+ return out;
4048
+ }
4049
+ function mergeDbUiPrefs(current, patch) {
4050
+ if (!isRecord(patch))
4051
+ return current;
4052
+ const next = { ...current ?? {} };
4053
+ for (const key of DB_UI_BOOL_PREF_KEYS) {
4054
+ const v = patch[key];
4055
+ if (v === null)
4056
+ delete next[key];
4057
+ else if (v === true || v === false)
4058
+ next[key] = v;
4059
+ }
4060
+ return Object.keys(next).length > 0 ? next : undefined;
4271
4061
  }
4272
4062
  function mergeDbUiState(current, patch) {
4273
4063
  if (!isRecord(patch))
4274
4064
  return current;
4065
+ const mergedPrefs = "prefs" in patch ? mergeDbUiPrefs(current.prefs, patch.prefs) : current.prefs;
4275
4066
  if (!isRecord(patch.columnWidths)) {
4276
- return sanitizeDbUiState({ ...current, ...patch, version: 1 });
4067
+ const merged = { ...current, version: 1 };
4068
+ if (mergedPrefs)
4069
+ merged.prefs = mergedPrefs;
4070
+ else
4071
+ delete merged.prefs;
4072
+ return sanitizeDbUiState(merged);
4277
4073
  }
4278
4074
  const columnWidths = {
4279
4075
  ...current.columnWidths
4280
4076
  };
4281
- for (const [dbId, tablesRaw] of Object.entries(patch.columnWidths)) {
4282
- if (tablesRaw === null) {
4283
- delete columnWidths[dbId];
4284
- continue;
4077
+ for (const [dbId, tablesRaw] of Object.entries(patch.columnWidths)) {
4078
+ if (tablesRaw === null) {
4079
+ delete columnWidths[dbId];
4080
+ continue;
4081
+ }
4082
+ if (!isRecord(tablesRaw))
4083
+ continue;
4084
+ const tables = { ...columnWidths[dbId] || {} };
4085
+ for (const [table, columnsRaw] of Object.entries(tablesRaw)) {
4086
+ if (columnsRaw === null) {
4087
+ delete tables[table];
4088
+ continue;
4089
+ }
4090
+ if (!isRecord(columnsRaw))
4091
+ continue;
4092
+ const columns = { ...tables[table] || {} };
4093
+ for (const [column, widthRaw] of Object.entries(columnsRaw)) {
4094
+ if (widthRaw === null)
4095
+ delete columns[column];
4096
+ else
4097
+ columns[column] = widthRaw;
4098
+ }
4099
+ tables[table] = columns;
4100
+ }
4101
+ columnWidths[dbId] = tables;
4102
+ }
4103
+ return sanitizeDbUiState({
4104
+ ...current,
4105
+ ...patch,
4106
+ columnWidths,
4107
+ prefs: mergedPrefs,
4108
+ version: 1
4109
+ });
4110
+ }
4111
+ async function loadAppSettingsState(root) {
4112
+ return settingsStore.load(root);
4113
+ }
4114
+ async function patchAppSettingsState(root, patch) {
4115
+ return settingsStore.update(root, (state) => {
4116
+ const next = mergeSettings(state, patch);
4117
+ return { state: next, result: next };
4118
+ });
4119
+ }
4120
+ async function loadViewState(root) {
4121
+ return viewStateStore.load(root);
4122
+ }
4123
+ async function patchViewState(root, patch) {
4124
+ return viewStateStore.update(root, (state) => {
4125
+ const next = mergeViewState(state, patch);
4126
+ return { state: next, result: next };
4127
+ });
4128
+ }
4129
+ async function loadDbUiState(root) {
4130
+ return dbUiStore.load(root);
4131
+ }
4132
+ async function patchDbUiState(root, patch) {
4133
+ return dbUiStore.update(root, (state) => {
4134
+ const next = mergeDbUiState(state, patch);
4135
+ return { state: next, result: next };
4136
+ });
4137
+ }
4138
+ var CODE_VIEWER_DIR2 = ".code-viewer", SETTINGS_FILE_NAME = "settings.json", VIEW_STATE_FILE_NAME = "view-state.json", DB_UI_FILE_NAME = "db-ui.json", MAX_SETTINGS_BYTES = 200000, MAX_VIEW_STATE_BYTES = 1e6, MAX_DB_UI_BYTES = 1e6, MAX_REF_LEN = 1024, MAX_KEY_LEN = 2048, MAX_VIEW_ITEMS = 20000, MAX_DB_UI_DBS = 200, MAX_DB_UI_TABLES = 500, MAX_DB_UI_COLUMNS = 1000, DB_UI_BOOL_PREF_KEYS, settingsStore, viewStateStore, dbUiStore;
4139
+ var init_state_store = __esm(() => {
4140
+ init_json_store();
4141
+ DB_UI_BOOL_PREF_KEYS = ["s3TooltipEnabled", "inferFkRails"];
4142
+ settingsStore = createJsonFileStore({
4143
+ filePath: (root) => codeViewerPath(root, SETTINGS_FILE_NAME),
4144
+ empty: emptySettings,
4145
+ sanitize: sanitizeSettings,
4146
+ maxBytes: MAX_SETTINGS_BYTES,
4147
+ backupSuffix: "corrupt",
4148
+ sizeErrorMessage: "settings state too large"
4149
+ });
4150
+ viewStateStore = createJsonFileStore({
4151
+ filePath: (root) => codeViewerPath(root, VIEW_STATE_FILE_NAME),
4152
+ empty: emptyViewState,
4153
+ sanitize: sanitizeViewState,
4154
+ maxBytes: MAX_VIEW_STATE_BYTES,
4155
+ backupSuffix: "corrupt",
4156
+ sizeErrorMessage: "view state too large"
4157
+ });
4158
+ dbUiStore = createJsonFileStore({
4159
+ filePath: (root) => codeViewerPath(root, DB_UI_FILE_NAME),
4160
+ empty: emptyDbUiState,
4161
+ sanitize: sanitizeDbUiState,
4162
+ maxBytes: MAX_DB_UI_BYTES,
4163
+ backupSuffix: "corrupt",
4164
+ sizeErrorMessage: "db UI state too large"
4165
+ });
4166
+ });
4167
+
4168
+ // web-src/server/worktree-watcher.ts
4169
+ import {
4170
+ lstatSync as lstatSync3,
4171
+ readdirSync as nodeReaddirSync,
4172
+ watch as nodeWatch
4173
+ } from "node:fs";
4174
+ import { join as join8, relative } from "node:path";
4175
+ function normalizeRelativePath(path) {
4176
+ return path.replace(/\\/g, "/").replace(/^\/+/, "");
4177
+ }
4178
+ function isInsideRoot(root, path) {
4179
+ const rel = relative(root, path).replace(/\\/g, "/");
4180
+ return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
4181
+ }
4182
+ function startWorktreeUpdateWatch(options) {
4183
+ const watch = options.watch || nodeWatch;
4184
+ const readDirs = options.readdirSync || ((path) => nodeReaddirSync(path, { withFileTypes: true }));
4185
+ const isDirectory = options.isDirectory || ((path) => {
4186
+ try {
4187
+ return lstatSync3(path).isDirectory();
4188
+ } catch {
4189
+ return false;
4190
+ }
4191
+ });
4192
+ const directorySignature = options.directorySignature || ((path) => {
4193
+ try {
4194
+ const stats = lstatSync3(path);
4195
+ if (!stats.isDirectory())
4196
+ return null;
4197
+ return `${stats.dev}:${stats.ino}`;
4198
+ } catch {
4199
+ return null;
4200
+ }
4201
+ });
4202
+ const setTimer = options.setTimeoutFn || setTimeout;
4203
+ const clearTimer = options.clearTimeoutFn || clearTimeout;
4204
+ const debounceMs = options.debounceMs ?? 250;
4205
+ const maxWatchedDirectories = Math.max(1, Math.floor(options.maxWatchedDirectories ?? DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT));
4206
+ const watchers = new Map;
4207
+ const signatures = new Map;
4208
+ const initialScanAsync = options.initialScanMode === "async" || (!options.watch || options.watch === nodeWatch) && !options.readdirSync;
4209
+ const initialScanQueue = [];
4210
+ let initialScanTimer = null;
4211
+ const pendingPathInspections = new Map;
4212
+ let pathInspectionTimer = null;
4213
+ let timer = null;
4214
+ const pendingChangedPaths = new Set;
4215
+ let watchLimitReported = false;
4216
+ const ignored = (path) => isSkippableSearchPath(normalizeRelativePath(path), options.omitDirNames, options.excludeNames);
4217
+ const directoryRelativePath = (dir) => normalizeRelativePath(relative(options.root, dir));
4218
+ const ignoredDirectory = (dir) => {
4219
+ const rel = directoryRelativePath(dir);
4220
+ return Boolean(rel && ignored(rel));
4221
+ };
4222
+ const scheduleUpdate = (changedPath) => {
4223
+ if (changedPath)
4224
+ pendingChangedPaths.add(changedPath);
4225
+ if (timer)
4226
+ clearTimer(timer);
4227
+ timer = setTimer(() => {
4228
+ timer = null;
4229
+ const paths = pendingChangedPaths.size ? [...pendingChangedPaths] : undefined;
4230
+ pendingChangedPaths.clear();
4231
+ options.onUpdate(paths);
4232
+ }, debounceMs);
4233
+ };
4234
+ const reportWatchLimit = () => {
4235
+ if (watchLimitReported)
4236
+ return;
4237
+ watchLimitReported = true;
4238
+ options.onWatchLimit?.(maxWatchedDirectories);
4239
+ options.onError?.(new Error(`worktree watcher cap reached (${maxWatchedDirectories}); subsequent changes may be missed`));
4240
+ };
4241
+ const closeSubtree = (dir) => {
4242
+ for (const [watchedDir, watcher] of [...watchers]) {
4243
+ if (watchedDir !== dir && !watchedDir.startsWith(`${dir}/`))
4244
+ continue;
4245
+ try {
4246
+ watcher.close?.();
4247
+ } catch {}
4248
+ watchers.delete(watchedDir);
4249
+ signatures.delete(watchedDir);
4250
+ }
4251
+ };
4252
+ const closeAll = () => {
4253
+ if (initialScanTimer) {
4254
+ clearTimer(initialScanTimer);
4255
+ initialScanTimer = null;
4256
+ }
4257
+ if (pathInspectionTimer) {
4258
+ clearTimer(pathInspectionTimer);
4259
+ pathInspectionTimer = null;
4260
+ }
4261
+ initialScanQueue.length = 0;
4262
+ pendingPathInspections.clear();
4263
+ for (const watcher of [...watchers.values()]) {
4264
+ try {
4265
+ watcher.close?.();
4266
+ } catch {}
4267
+ }
4268
+ watchers.clear();
4269
+ signatures.clear();
4270
+ };
4271
+ const readChildDirectories = (dir) => {
4272
+ let entries;
4273
+ try {
4274
+ entries = readDirs(dir);
4275
+ } catch (error) {
4276
+ options.onError?.(error);
4277
+ return [];
4278
+ }
4279
+ const children = [];
4280
+ for (const entry of entries) {
4281
+ if (!entry.isDirectory())
4282
+ continue;
4283
+ const child = join8(dir, entry.name);
4284
+ if (ignoredDirectory(child))
4285
+ continue;
4286
+ children.push(child);
4287
+ }
4288
+ return children;
4289
+ };
4290
+ const processInitialScanQueue = () => {
4291
+ initialScanTimer = null;
4292
+ if (watchers.size >= maxWatchedDirectories) {
4293
+ reportWatchLimit();
4294
+ initialScanQueue.length = 0;
4295
+ return;
4296
+ }
4297
+ const next = initialScanQueue.shift();
4298
+ if (next)
4299
+ watchDirectory(next, true);
4300
+ if (watchers.size >= maxWatchedDirectories) {
4301
+ reportWatchLimit();
4302
+ initialScanQueue.length = 0;
4303
+ }
4304
+ if (initialScanQueue.length)
4305
+ initialScanTimer = setTimer(processInitialScanQueue, 50);
4306
+ };
4307
+ const queueInitialChildren = (dir) => {
4308
+ const remaining = maxWatchedDirectories - watchers.size;
4309
+ if (remaining <= 0) {
4310
+ reportWatchLimit();
4311
+ return;
4312
+ }
4313
+ const children = readChildDirectories(dir);
4314
+ if (children.length > remaining)
4315
+ reportWatchLimit();
4316
+ initialScanQueue.push(...children.slice(0, remaining));
4317
+ if (!initialScanTimer)
4318
+ initialScanTimer = setTimer(processInitialScanQueue, 5000);
4319
+ };
4320
+ const processChangedPath = (changed, fullChangedPath) => {
4321
+ const known = watchers.has(fullChangedPath);
4322
+ if (isDirectory(fullChangedPath)) {
4323
+ if (known) {
4324
+ const signature = directorySignature(fullChangedPath);
4325
+ if (signature && signature !== signatures.get(fullChangedPath)) {
4326
+ closeSubtree(fullChangedPath);
4327
+ watchDirectory(fullChangedPath, initialScanAsync);
4328
+ }
4329
+ scheduleUpdate(changed);
4330
+ return;
4331
+ }
4332
+ watchDirectory(fullChangedPath, initialScanAsync);
4333
+ } else if (known) {
4334
+ closeSubtree(fullChangedPath);
4335
+ }
4336
+ scheduleUpdate(changed);
4337
+ };
4338
+ const processPathInspections = () => {
4339
+ pathInspectionTimer = null;
4340
+ const entries = [...pendingPathInspections];
4341
+ pendingPathInspections.clear();
4342
+ for (const [changed, fullChangedPath] of entries) {
4343
+ processChangedPath(changed, fullChangedPath);
4344
+ }
4345
+ };
4346
+ const queuePathInspection = (changed, fullChangedPath) => {
4347
+ pendingPathInspections.set(changed, fullChangedPath);
4348
+ if (!pathInspectionTimer)
4349
+ pathInspectionTimer = setTimer(processPathInspections, 25);
4350
+ };
4351
+ const watchDirectory = (dir, initialScan = false) => {
4352
+ if (watchers.has(dir))
4353
+ return;
4354
+ if (watchers.size >= maxWatchedDirectories) {
4355
+ reportWatchLimit();
4356
+ return;
4285
4357
  }
4286
- if (!isRecord(tablesRaw))
4287
- continue;
4288
- const tables = { ...columnWidths[dbId] || {} };
4289
- for (const [table, columnsRaw] of Object.entries(tablesRaw)) {
4290
- if (columnsRaw === null) {
4291
- delete tables[table];
4292
- continue;
4293
- }
4294
- if (!isRecord(columnsRaw))
4295
- continue;
4296
- const columns = { ...tables[table] || {} };
4297
- for (const [column, widthRaw] of Object.entries(columnsRaw)) {
4298
- if (widthRaw === null)
4299
- delete columns[column];
4300
- else
4301
- columns[column] = widthRaw;
4302
- }
4303
- tables[table] = columns;
4358
+ const rel = directoryRelativePath(dir);
4359
+ if (rel && ignored(rel))
4360
+ return;
4361
+ try {
4362
+ const watcher = watch(dir, { persistent: false }, (_event, filename) => {
4363
+ if (!filename) {
4364
+ scheduleUpdate();
4365
+ return;
4366
+ }
4367
+ const changed = normalizeRelativePath(join8(rel, filename.toString()));
4368
+ if (ignored(changed))
4369
+ return;
4370
+ const fullChangedPath = join8(options.root, changed);
4371
+ if (!isInsideRoot(options.root, fullChangedPath))
4372
+ return;
4373
+ if (initialScanAsync) {
4374
+ queuePathInspection(changed, fullChangedPath);
4375
+ return;
4376
+ }
4377
+ processChangedPath(changed, fullChangedPath);
4378
+ }) || {};
4379
+ watchers.set(dir, watcher);
4380
+ const signature = directorySignature(dir);
4381
+ if (signature)
4382
+ signatures.set(dir, signature);
4383
+ watcher.on?.("error", () => {
4384
+ if (watchers.get(dir) === watcher) {
4385
+ watchers.delete(dir);
4386
+ signatures.delete(dir);
4387
+ }
4388
+ });
4389
+ watcher.on?.("close", () => {
4390
+ if (watchers.get(dir) === watcher) {
4391
+ watchers.delete(dir);
4392
+ signatures.delete(dir);
4393
+ }
4394
+ });
4395
+ } catch (error) {
4396
+ options.onError?.(error);
4397
+ return;
4304
4398
  }
4305
- columnWidths[dbId] = tables;
4306
- }
4307
- return sanitizeDbUiState({ ...current, ...patch, columnWidths, version: 1 });
4308
- }
4309
- async function loadAppSettingsState(root) {
4310
- return settingsStore.load(root);
4311
- }
4312
- async function patchAppSettingsState(root, patch) {
4313
- return settingsStore.update(root, (state) => {
4314
- const next = mergeSettings(state, patch);
4315
- return { state: next, result: next };
4316
- });
4317
- }
4318
- async function loadViewState(root) {
4319
- return viewStateStore.load(root);
4399
+ if (initialScanAsync && initialScan) {
4400
+ queueInitialChildren(dir);
4401
+ return;
4402
+ }
4403
+ if (watchers.size >= maxWatchedDirectories) {
4404
+ reportWatchLimit();
4405
+ return;
4406
+ }
4407
+ for (const child of readChildDirectories(dir))
4408
+ watchDirectory(child);
4409
+ };
4410
+ watchDirectory(options.root, true);
4411
+ return { started: watchers.size > 0, close: closeAll };
4320
4412
  }
4321
- async function patchViewState(root, patch) {
4322
- return viewStateStore.update(root, (state) => {
4323
- const next = mergeViewState(state, patch);
4324
- return { state: next, result: next };
4325
- });
4413
+ var DEFAULT_WORKTREE_WATCH_DIRECTORY_LIMIT = 256;
4414
+ var init_worktree_watcher = __esm(() => {
4415
+ init_search();
4416
+ });
4417
+
4418
+ // web-src/core/control-chars.ts
4419
+ function hasControlCharacter(value) {
4420
+ for (const ch of value) {
4421
+ const code = ch.charCodeAt(0);
4422
+ if (code < 32 || code === 127)
4423
+ return true;
4424
+ }
4425
+ return false;
4326
4426
  }
4327
- async function loadDbUiState(root) {
4328
- return dbUiStore.load(root);
4427
+
4428
+ // web-src/core/id.ts
4429
+ function bytesToHex(bytes) {
4430
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
4329
4431
  }
4330
- async function patchDbUiState(root, patch) {
4331
- return dbUiStore.update(root, (state) => {
4332
- const next = mergeDbUiState(state, patch);
4333
- return { state: next, result: next };
4334
- });
4432
+ function makeId(prefix) {
4433
+ const cryptoApi = globalThis.crypto;
4434
+ if (typeof cryptoApi?.randomUUID === "function") {
4435
+ return `${prefix}-${cryptoApi.randomUUID().replace(/-/g, "").slice(0, 16)}`;
4436
+ }
4437
+ if (typeof cryptoApi?.getRandomValues === "function") {
4438
+ const bytes = new Uint8Array(8);
4439
+ cryptoApi.getRandomValues(bytes);
4440
+ return `${prefix}-${bytesToHex(bytes)}`;
4441
+ }
4442
+ return `${prefix}-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
4335
4443
  }
4336
- var CODE_VIEWER_DIR2 = ".code-viewer", SETTINGS_FILE_NAME = "settings.json", VIEW_STATE_FILE_NAME = "view-state.json", DB_UI_FILE_NAME = "db-ui.json", MAX_SETTINGS_BYTES = 200000, MAX_VIEW_STATE_BYTES = 1e6, MAX_DB_UI_BYTES = 1e6, MAX_REF_LEN = 1024, MAX_KEY_LEN = 2048, MAX_VIEW_ITEMS = 20000, MAX_DB_UI_DBS = 200, MAX_DB_UI_TABLES = 500, MAX_DB_UI_COLUMNS = 1000, settingsStore, viewStateStore, dbUiStore;
4337
- var init_state_store = __esm(() => {
4338
- init_json_store();
4339
- settingsStore = createJsonFileStore({
4340
- filePath: (root) => codeViewerPath(root, SETTINGS_FILE_NAME),
4341
- empty: emptySettings,
4342
- sanitize: sanitizeSettings,
4343
- maxBytes: MAX_SETTINGS_BYTES,
4344
- backupSuffix: "corrupt",
4345
- sizeErrorMessage: "settings state too large"
4346
- });
4347
- viewStateStore = createJsonFileStore({
4348
- filePath: (root) => codeViewerPath(root, VIEW_STATE_FILE_NAME),
4349
- empty: emptyViewState,
4350
- sanitize: sanitizeViewState,
4351
- maxBytes: MAX_VIEW_STATE_BYTES,
4352
- backupSuffix: "corrupt",
4353
- sizeErrorMessage: "view state too large"
4354
- });
4355
- dbUiStore = createJsonFileStore({
4356
- filePath: (root) => codeViewerPath(root, DB_UI_FILE_NAME),
4357
- empty: emptyDbUiState,
4358
- sanitize: sanitizeDbUiState,
4359
- maxBytes: MAX_DB_UI_BYTES,
4360
- backupSuffix: "corrupt",
4361
- sizeErrorMessage: "db UI state too large"
4362
- });
4363
- });
4364
4444
 
4365
4445
  // web-src/server/database/adapters/abort.ts
4366
4446
  function abortError(message = "operation aborted") {
@@ -4528,25 +4608,23 @@ function sanitizeIdentifier(name, kind = "sqlite") {
4528
4608
  return `\`${name.replace(/`/g, "``")}\``;
4529
4609
  return `"${name.replace(/"/g, '""')}"`;
4530
4610
  }
4531
- function escapeSqlString(value) {
4532
- return `'${value.replace(/'/g, "''")}'`;
4611
+ function escapeSqlString(value, kind) {
4612
+ const escaped = kind === "mysql" ? value.replace(/\\/g, "\\\\").replace(/'/g, "''") : value.replace(/'/g, "''");
4613
+ return `'${escaped}'`;
4533
4614
  }
4534
- function buildFilterWhere(grouped, kind) {
4615
+ function buildFilterWhere(grouped, kind, exact) {
4535
4616
  const whereParts = [];
4536
4617
  const params = [];
4537
4618
  const useParams = kind === "sqlite";
4619
+ const castOf = (column) => kind === "mysql" ? `CAST(${sanitizeIdentifier(column, kind)} AS CHAR)` : `CAST(${sanitizeIdentifier(column, kind)} AS TEXT)`;
4538
4620
  for (const [value, cols] of grouped) {
4539
- const likeVal = useParams ? "?" : escapeSqlString(`%${value}%`);
4621
+ const likeVal = useParams ? "?" : escapeSqlString(`%${value}%`, kind);
4540
4622
  if (cols.length === 1) {
4541
- const cast = kind === "mysql" ? `CAST(${sanitizeIdentifier(cols[0], kind)} AS CHAR)` : `CAST(${sanitizeIdentifier(cols[0], kind)} AS TEXT)`;
4542
- whereParts.push(`${cast} LIKE ${likeVal}`);
4623
+ whereParts.push(`${castOf(cols[0])} LIKE ${likeVal}`);
4543
4624
  if (useParams)
4544
4625
  params.push(`%${value}%`);
4545
4626
  } else {
4546
- const orParts = cols.map((column) => {
4547
- const cast = kind === "mysql" ? `CAST(${sanitizeIdentifier(column, kind)} AS CHAR)` : `CAST(${sanitizeIdentifier(column, kind)} AS TEXT)`;
4548
- return `${cast} LIKE ${likeVal}`;
4549
- });
4627
+ const orParts = cols.map((column) => `${castOf(column)} LIKE ${likeVal}`);
4550
4628
  whereParts.push(`(${orParts.join(" OR ")})`);
4551
4629
  if (useParams) {
4552
4630
  for (let i = 0;i < cols.length; i++)
@@ -4554,6 +4632,12 @@ function buildFilterWhere(grouped, kind) {
4554
4632
  }
4555
4633
  }
4556
4634
  }
4635
+ for (const cond of exact ?? []) {
4636
+ const rhs = useParams ? "?" : escapeSqlString(cond.value, kind);
4637
+ whereParts.push(`${castOf(cond.column)} = ${rhs}`);
4638
+ if (useParams)
4639
+ params.push(cond.value);
4640
+ }
4557
4641
  return { where: whereParts.join(" AND "), params, useParams };
4558
4642
  }
4559
4643
  function filterGroupedColumns(grouped, columnNames) {
@@ -4566,6 +4650,12 @@ function filterGroupedColumns(grouped, columnNames) {
4566
4650
  }
4567
4651
  return filtered;
4568
4652
  }
4653
+ function filterExactColumns(exact, columnNames) {
4654
+ if (!exact || exact.length === 0)
4655
+ return [];
4656
+ const validColumns = new Set(columnNames);
4657
+ return exact.filter((cond) => validColumns.has(cond.column));
4658
+ }
4569
4659
  function filterOrderByColumns(orderBy, columnNames) {
4570
4660
  if (!orderBy)
4571
4661
  return;
@@ -4869,6 +4959,8 @@ function buildExecArgs(config, sql) {
4869
4959
  "-A",
4870
4960
  "-F",
4871
4961
  "\t",
4962
+ "-R",
4963
+ PG_RECORD_SEPARATOR,
4872
4964
  "-v",
4873
4965
  "ON_ERROR_STOP=1",
4874
4966
  "-c",
@@ -5044,11 +5136,14 @@ function splitTsvLine(line, decodeFields) {
5044
5136
  const fields = line.split("\t");
5045
5137
  return decodeFields ? fields.map(decodeMysqlBatchField) : fields;
5046
5138
  }
5047
- function parseTsvOutput(stdout, hasHeader) {
5048
- const text = stripFinalLineBreak(stdout);
5139
+ function stripFinalRecordSeparator(text, recordSeparator) {
5140
+ return text.endsWith(recordSeparator) ? text.slice(0, -recordSeparator.length) : text;
5141
+ }
5142
+ function parseTsvOutput(stdout, hasHeader, recordSeparator) {
5143
+ const text = recordSeparator ? stripFinalRecordSeparator(stdout, recordSeparator) : stripFinalLineBreak(stdout);
5049
5144
  if (text.length === 0)
5050
5145
  return { columns: [], rows: [] };
5051
- const lines = text.split(/\r?\n/);
5146
+ const lines = recordSeparator ? text.split(recordSeparator) : text.split(/\r?\n/);
5052
5147
  if (lines.length === 0)
5053
5148
  return { columns: [], rows: [] };
5054
5149
  if (hasHeader) {
@@ -5125,7 +5220,7 @@ function createDockerAdapter(config) {
5125
5220
  if (result.code !== 0) {
5126
5221
  throw new Error(result.stderr.trim() || "query failed");
5127
5222
  }
5128
- return parseTsvOutput(result.stdout, config.kind === "mysql");
5223
+ return parseTsvOutput(result.stdout, config.kind === "mysql", config.kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined);
5129
5224
  }
5130
5225
  function toDbValue(val) {
5131
5226
  if (val === "NULL" || val === "\\N")
@@ -5218,25 +5313,18 @@ function createDockerAdapter(config) {
5218
5313
  },
5219
5314
  async getIndexesAsync(signal) {
5220
5315
  let sql;
5316
+ const INDEX_COL_SEP = "\x1F";
5221
5317
  if (config.kind === "postgresql") {
5222
- sql = `SELECT indexname, tablename FROM pg_indexes WHERE schemaname = ${postgresSchemaLiteral()} AND indexname NOT LIKE 'pg_%' ORDER BY indexname`;
5318
+ sql = `SELECT i.relname, t.relname, CASE WHEN ix.indisunique THEN '1' ELSE '0' END, COALESCE(string_agg(a.attname, E'\\x1f' ORDER BY k.ord), '') FROM pg_index ix JOIN pg_class i ON i.oid = ix.indexrelid JOIN pg_class t ON t.oid = ix.indrelid JOIN pg_namespace n ON n.oid = t.relnamespace LEFT JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, ord) ON true LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum WHERE n.nspname = ${postgresSchemaLiteral()} AND i.relname NOT LIKE 'pg_%' GROUP BY i.relname, t.relname, ix.indisunique ORDER BY t.relname, i.relname`;
5223
5319
  } else {
5224
- sql = `SELECT DISTINCT index_name, table_name, non_unique FROM information_schema.statistics WHERE table_schema = DATABASE() ORDER BY index_name`;
5320
+ sql = `SELECT index_name, table_name, IF(MAX(non_unique) = 0, '1', '0'), GROUP_CONCAT(column_name ORDER BY seq_in_index SEPARATOR '\x1F') FROM information_schema.statistics WHERE table_schema = DATABASE() GROUP BY index_name, table_name ORDER BY table_name, index_name`;
5225
5321
  }
5226
5322
  const result = await execAsync(sql, signal);
5227
- if (config.kind === "postgresql") {
5228
- return result.rows.map((row) => ({
5229
- name: row[0],
5230
- table: row[1],
5231
- columns: [],
5232
- unique: false
5233
- }));
5234
- }
5235
5323
  return result.rows.map((row) => ({
5236
5324
  name: row[0],
5237
5325
  table: row[1],
5238
- columns: [],
5239
- unique: row[2] === "0"
5326
+ unique: row[2] === "1",
5327
+ columns: row[3] ? row[3].split(INDEX_COL_SEP).filter((s) => s.length > 0) : []
5240
5328
  }));
5241
5329
  },
5242
5330
  async getForeignKeysAsync(signal) {
@@ -5426,7 +5514,7 @@ function createDockerAdapter(config) {
5426
5514
  }
5427
5515
  const columnNames = columns.map((column) => column.name);
5428
5516
  const order = buildOrderClause(filterOrderByColumns(options.orderBy, columnNames), config.kind);
5429
- const where = buildFilterWhere(filterGroupedColumns(options.grouped, columnNames), config.kind).where;
5517
+ const where = buildFilterWhere(filterGroupedColumns(options.grouped, columnNames), config.kind, filterExactColumns(options.exact, columnNames)).where;
5430
5518
  const whereClause = where ? ` WHERE ${where}` : "";
5431
5519
  const countSql = `SELECT COUNT(*) AS cnt FROM ${id}${whereClause}`;
5432
5520
  const countResultPromise = execAsync(countSql, signal);
@@ -5606,7 +5694,7 @@ async function listDockerDatabasesAsync(serviceName, kind, env, cwd, signal) {
5606
5694
  return fallback;
5607
5695
  return setDockerDatabasesCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
5608
5696
  }
5609
- const parsed = parseTsvOutput(result.stdout, kind === "mysql");
5697
+ const parsed = parseTsvOutput(result.stdout, kind === "mysql", kind === "postgresql" ? PG_RECORD_SEPARATOR : undefined);
5610
5698
  const dbs = parsed.rows.map((r) => r[0]).filter(Boolean);
5611
5699
  const value = dbs.length > 0 ? dbs : fallbackDockerDatabases(defaultDb);
5612
5700
  return setDockerDatabasesCache(cacheKey, value, value.length > 0 ? DOCKER_DATABASES_POSITIVE_TTL_MS : DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
@@ -5647,7 +5735,7 @@ async function listDockerSchemasAsync(serviceName, kind, env, cwd, overrideDatab
5647
5735
  if (result.code !== 0) {
5648
5736
  return setDockerSchemasCache(cacheKey, ["public"], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
5649
5737
  }
5650
- const parsed = parseTsvOutput(result.stdout, false);
5738
+ const parsed = parseTsvOutput(result.stdout, false, PG_RECORD_SEPARATOR);
5651
5739
  const schemas = parsed.rows.map((r) => r[0]).filter(Boolean);
5652
5740
  const value = schemas.length > 0 ? schemas : ["public"];
5653
5741
  return setDockerSchemasCache(cacheKey, value, DOCKER_DATABASES_POSITIVE_TTL_MS, now);
@@ -5671,7 +5759,7 @@ async function openDockerAdapterAsync(serviceName, kind, env, cwd, overrideDatab
5671
5759
  ...kind === "postgresql" && schema ? { schema } : {}
5672
5760
  });
5673
5761
  }
5674
- var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, MYSQL_SPATIAL_TYPES;
5762
+ var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, PG_RECORD_SEPARATOR = "\x1E", MYSQL_SPATIAL_TYPES;
5675
5763
  var init_docker = __esm(() => {
5676
5764
  init_sql_snapshot();
5677
5765
  init_docker_utils();
@@ -5870,7 +5958,7 @@ function createSqliteAdapter(db) {
5870
5958
  async getFilteredTablePageWithMeta(table, options) {
5871
5959
  const columns = queryColumns(db, table);
5872
5960
  const columnNames = columns.map((column) => column.name);
5873
- const filter = buildFilterWhere(filterGroupedColumns(options.grouped, columnNames), "sqlite");
5961
+ const filter = buildFilterWhere(filterGroupedColumns(options.grouped, columnNames), "sqlite", filterExactColumns(options.exact, columnNames));
5874
5962
  const order = buildOrderClause2(filterOrderByColumns(options.orderBy, columnNames));
5875
5963
  const tableId = sanitizeIdentifier(table);
5876
5964
  const whereClause = filter.where ? ` WHERE ${filter.where}` : "";
@@ -5881,7 +5969,7 @@ function createSqliteAdapter(db) {
5881
5969
  columns,
5882
5970
  rows: result.rows,
5883
5971
  rowCount: result.rowCount,
5884
- totalRows: countRow?.cnt ?? 0
5972
+ totalRows: Number(countRow?.cnt ?? 0)
5885
5973
  };
5886
5974
  },
5887
5975
  executeReadonlyQuery(sql, params, maxRows = 1000) {
@@ -8570,9 +8658,16 @@ function parseBuckets(xml) {
8570
8658
  };
8571
8659
  }).filter((bucket) => bucket.name).sort((a, b) => a.name.localeCompare(b.name));
8572
8660
  }
8661
+ function decodeS3UrlEncoded(value) {
8662
+ try {
8663
+ return decodeURIComponent(value);
8664
+ } catch {
8665
+ return value;
8666
+ }
8667
+ }
8573
8668
  function parseObjects(xml) {
8574
8669
  const objects = xmlBlocks(xml, "Contents").map((block) => {
8575
- const key = xmlText(block, "Key") || "";
8670
+ const key = decodeS3UrlEncoded(xmlText(block, "Key") || "");
8576
8671
  const updatedAt = toIsoDate(xmlText(block, "LastModified"));
8577
8672
  return {
8578
8673
  key,
@@ -8582,8 +8677,10 @@ function parseObjects(xml) {
8582
8677
  ...xmlText(block, "StorageClass") ? { storageClass: xmlText(block, "StorageClass") } : {}
8583
8678
  };
8584
8679
  }).filter((object) => object.key);
8680
+ const commonPrefixes = xmlBlocks(xml, "CommonPrefixes").map((block) => decodeS3UrlEncoded(xmlText(block, "Prefix") || "")).filter(Boolean);
8585
8681
  return {
8586
8682
  objects,
8683
+ commonPrefixes,
8587
8684
  nextToken: xmlText(xml, "NextContinuationToken"),
8588
8685
  truncated: xmlText(xml, "IsTruncated") === "true"
8589
8686
  };
@@ -8665,8 +8762,10 @@ function createS3Adapter(config) {
8665
8762
  bucket: opts.bucket,
8666
8763
  query: {
8667
8764
  "list-type": "2",
8765
+ "encoding-type": "url",
8668
8766
  "max-keys": String(Math.min(1000, Math.max(1, opts.maxKeys ?? 200))),
8669
8767
  ...opts.prefix ? { prefix: opts.prefix } : {},
8768
+ ...opts.delimiter ? { delimiter: opts.delimiter } : {},
8670
8769
  ...opts.continuationToken ? { "continuation-token": opts.continuationToken } : {}
8671
8770
  },
8672
8771
  signal: opts.signal
@@ -9024,6 +9123,42 @@ ${s3ObjectName(object.key)}`.toLowerCase();
9024
9123
  return s3ErrorResponse(err, "list s3 objects");
9025
9124
  }
9026
9125
  }
9126
+ async function handleFolder(cwd, req, url, omitDirNames) {
9127
+ const r = await resolveS3(cwd, url.searchParams.get("db"), req.signal, omitDirNames);
9128
+ if (r instanceof Response)
9129
+ return r;
9130
+ const bucket = validateBucket(url.searchParams.get("bucket"));
9131
+ if (bucket instanceof Response)
9132
+ return bucket;
9133
+ const prefix = validateOptionalText(url.searchParams.get("prefix"), "prefix", 2048);
9134
+ if (prefix instanceof Response)
9135
+ return prefix;
9136
+ const token = validateOptionalText(url.searchParams.get("token"), "token", 4096);
9137
+ if (token instanceof Response)
9138
+ return token;
9139
+ try {
9140
+ const page = await r.explorer.listObjects({
9141
+ bucket,
9142
+ prefix,
9143
+ delimiter: "/",
9144
+ continuationToken: token || undefined,
9145
+ maxKeys: MAX_OBJECT_LIMIT,
9146
+ signal: req.signal
9147
+ });
9148
+ const objects = page.objects.filter((object) => object.key !== prefix);
9149
+ const body = {
9150
+ dbId: r.dbId,
9151
+ bucket,
9152
+ prefix,
9153
+ folders: page.commonPrefixes ?? [],
9154
+ objects,
9155
+ ...page.nextToken ? { nextToken: page.nextToken } : {}
9156
+ };
9157
+ return json(body);
9158
+ } catch (err) {
9159
+ return s3ErrorResponse(err, "list s3 folder");
9160
+ }
9161
+ }
9027
9162
  async function handleHead(cwd, req, url, omitDirNames) {
9028
9163
  const r = await resolveS3(cwd, url.searchParams.get("db"), req.signal, omitDirNames);
9029
9164
  if (r instanceof Response)
@@ -9109,6 +9244,10 @@ async function handleS3Route(req, url, cwd, sideEffectAllowed, omitDirNames) {
9109
9244
  methods: ["GET"],
9110
9245
  handler: () => handleObjects(cwd, req, url, omitDirNames)
9111
9246
  },
9247
+ "/_db/s3/folder": {
9248
+ methods: ["GET"],
9249
+ handler: () => handleFolder(cwd, req, url, omitDirNames)
9250
+ },
9112
9251
  "/_db/s3/head": {
9113
9252
  methods: ["GET"],
9114
9253
  handler: () => handleHead(cwd, req, url, omitDirNames)
@@ -9803,6 +9942,9 @@ function sanitize(input) {
9803
9942
  const sidebarWidth = sanitizeCssSize(tab.sidebarWidth);
9804
9943
  if (sidebarWidth !== undefined)
9805
9944
  out.sidebarWidth = sidebarWidth;
9945
+ const relatedPanelHeight = sanitizeCssSize(tab.relatedPanelHeight);
9946
+ if (relatedPanelHeight !== undefined)
9947
+ out.relatedPanelHeight = relatedPanelHeight;
9806
9948
  const redis = sanitizeRedis(tab.redis);
9807
9949
  if (redis !== undefined)
9808
9950
  out.redis = redis;
@@ -10043,19 +10185,25 @@ async function handleSchema(cwd, url, omitDirNames, signal) {
10043
10185
  linkedAbort.cleanup();
10044
10186
  }
10045
10187
  }
10046
- function parseFilters(url) {
10047
- const raw = url.searchParams.get("filters");
10188
+ function parseColumnValuePairs(url, param) {
10189
+ const raw = url.searchParams.get(param);
10048
10190
  if (!raw)
10049
10191
  return [];
10050
10192
  try {
10051
10193
  const parsed = JSON.parse(raw);
10052
10194
  if (!Array.isArray(parsed))
10053
10195
  return [];
10054
- return parsed.filter((f) => !!f && typeof f === "object" && typeof f.column === "string" && typeof f.value === "string");
10196
+ return parsed.filter((f) => !!f && typeof f === "object" && typeof f.column === "string" && typeof f.value === "string").filter((f) => f.column.length <= MAX_FILTER_COLUMN_LEN && f.value.length <= MAX_FILTER_VALUE_LEN).slice(0, MAX_COLUMN_VALUE_PAIRS);
10055
10197
  } catch {
10056
10198
  return [];
10057
10199
  }
10058
10200
  }
10201
+ function parseFilters(url) {
10202
+ return parseColumnValuePairs(url, "filters");
10203
+ }
10204
+ function parseExactConditions(url) {
10205
+ return parseColumnValuePairs(url, "eq");
10206
+ }
10059
10207
  function groupFiltersByValue(filters) {
10060
10208
  const grouped = new Map;
10061
10209
  for (const filter of filters) {
@@ -10086,14 +10234,16 @@ async function handleTable(cwd, url, omitDirNames, signal) {
10086
10234
  ];
10087
10235
  }
10088
10236
  const filters = parseFilters(url);
10237
+ const exact = parseExactConditions(url);
10089
10238
  try {
10090
10239
  const adapter = await getAdapter(r, cwd, signal);
10091
- if (filters.length > 0) {
10240
+ if (filters.length > 0 || exact.length > 0) {
10092
10241
  const meta2 = await adapter.getFilteredTablePageWithMeta(table, {
10093
10242
  offset,
10094
10243
  limit,
10095
10244
  orderBy,
10096
- grouped: groupFiltersByValue(filters)
10245
+ grouped: groupFiltersByValue(filters),
10246
+ ...exact.length > 0 ? { exact } : {}
10097
10247
  }, signal);
10098
10248
  const colNames2 = new Set(meta2.columns.map((c) => c.name));
10099
10249
  if (sortCol && !colNames2.has(sortCol)) {
@@ -10367,6 +10517,7 @@ async function handleExport(cwd, url, omitDirNames, signal) {
10367
10517
  ];
10368
10518
  }
10369
10519
  const filters = parseFilters(url);
10520
+ const exact = parseExactConditions(url);
10370
10521
  try {
10371
10522
  const adapter = await getAdapter(r, cwd, signal);
10372
10523
  const db = asAsync(adapter);
@@ -10377,12 +10528,13 @@ async function handleExport(cwd, url, omitDirNames, signal) {
10377
10528
  return textError(`invalid sort column: ${sortCol}`, 400);
10378
10529
  }
10379
10530
  let rawRows;
10380
- if (filters.length > 0) {
10531
+ if (filters.length > 0 || exact.length > 0) {
10381
10532
  const meta = await adapter.getFilteredTablePageWithMeta(table, {
10382
10533
  offset: 0,
10383
10534
  limit: EXPORT_MAX_ROWS,
10384
10535
  orderBy,
10385
- grouped: groupFiltersByValue(filters)
10536
+ grouped: groupFiltersByValue(filters),
10537
+ ...exact.length > 0 ? { exact } : {}
10386
10538
  }, signal);
10387
10539
  rawRows = meta.rows;
10388
10540
  } else {
@@ -11019,7 +11171,7 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
11019
11171
  }
11020
11172
  }, sideEffectAllowed, wrapResponse, (err) => handleError("database", "handle database request", err));
11021
11173
  }
11022
- var initialized = false, dockerAdapterCache, MAX_SCHEMA_NAME_LEN2 = 1024, EXPORT_MAX_ROWS = 1e5, MAX_TABS_BODY_BYTES = 1e6, MAX_DB_UI_BODY_BYTES = 1e6, MAX_SNAPSHOT_TABLES = 512, MAX_SNAPSHOT_TABLE_NAME_LEN = 1024, searchJobs, snapshotJobs, DOCKER_CLOSE_REGISTRY, SNAPSHOT_DOCKER_SOURCE_REGISTRY;
11174
+ var initialized = false, dockerAdapterCache, MAX_SCHEMA_NAME_LEN2 = 1024, MAX_COLUMN_VALUE_PAIRS = 64, MAX_FILTER_COLUMN_LEN = 128, MAX_FILTER_VALUE_LEN = 4096, EXPORT_MAX_ROWS = 1e5, MAX_TABS_BODY_BYTES = 1e6, MAX_DB_UI_BODY_BYTES = 1e6, MAX_SNAPSHOT_TABLES = 512, MAX_SNAPSHOT_TABLE_NAME_LEN = 1024, searchJobs, snapshotJobs, DOCKER_CLOSE_REGISTRY, SNAPSHOT_DOCKER_SOURCE_REGISTRY;
11023
11175
  var init_handle = __esm(() => {
11024
11176
  init_state_store();
11025
11177
  init_docker();
@@ -11086,12 +11238,20 @@ async function parseJsonBody(req) {
11086
11238
  async function handleSettingsGet(cwd) {
11087
11239
  return jsonLoadResponse(() => loadAppSettingsState(cwd), "state", "failed to load settings state");
11088
11240
  }
11089
- async function handleSettingsPatch(cwd, req) {
11241
+ async function handleSettingsPatch(cwd, req, onChange) {
11090
11242
  const body = await parseJsonBody(req);
11091
11243
  if (body instanceof Response)
11092
11244
  return body;
11093
11245
  try {
11094
- return json(await patchAppSettingsState(cwd, body));
11246
+ const next = await patchAppSettingsState(cwd, body);
11247
+ if (onChange) {
11248
+ try {
11249
+ onChange(next);
11250
+ } catch (notifyErr) {
11251
+ console.warn("[code-viewer] settings change notify failed:", notifyErr);
11252
+ }
11253
+ }
11254
+ return json(next);
11095
11255
  } catch (err) {
11096
11256
  const message = err instanceof Error ? err.message : String(err);
11097
11257
  if (message === "settings state too large")
@@ -11117,12 +11277,12 @@ async function handleViewPatch(cwd, req) {
11117
11277
  return textError("failed to save view state", 500);
11118
11278
  }
11119
11279
  }
11120
- async function handleStateRoute(req, url, cwd, sideEffectAllowed) {
11280
+ async function handleStateRoute(req, url, cwd, sideEffectAllowed, options = {}) {
11121
11281
  return dispatchRoutes(req, url, {
11122
11282
  "/_state/settings": {
11123
11283
  methods: ["GET", "PATCH"],
11124
11284
  sideEffect: (method) => method !== "GET",
11125
- handler: () => req.method === "GET" ? handleSettingsGet(cwd) : handleSettingsPatch(cwd, req)
11285
+ handler: () => req.method === "GET" ? handleSettingsGet(cwd) : handleSettingsPatch(cwd, req, options.onSettingsChange)
11126
11286
  },
11127
11287
  "/_state/view": {
11128
11288
  methods: ["GET", "PATCH"],
@@ -11218,16 +11378,30 @@ Examples:
11218
11378
  }
11219
11379
  if (rest.length)
11220
11380
  cliArgs = rest;
11221
- const configScopeOmitDirs = loadProjectConfigScopeOmitDirs();
11222
- const configScopeExcludeNames = loadProjectConfigScopeExcludeNames();
11223
- uploadDisabledByConfig = loadProjectConfigUploadDisabled();
11381
+ warnIfLegacyConfigPresent();
11224
11382
  if (scopeOmitDirCliOverride) {
11225
11383
  scopeOmitDirNames = scopeOmitDirCliOverride;
11226
- } else if (configScopeOmitDirs) {
11227
- scopeOmitDirNames = configScopeOmitDirs;
11228
11384
  }
11229
- if (configScopeExcludeNames)
11230
- scopeExcludeNames = configScopeExcludeNames;
11385
+ }
11386
+ function warnIfLegacyConfigPresent() {
11387
+ try {
11388
+ if (existsSync6(join13(cwd, ".code-viewer.json"))) {
11389
+ console.warn("[code-viewer] .code-viewer.json is no longer used; configure scope and upload from Viewer Settings instead. The file can be safely removed.");
11390
+ }
11391
+ } catch {}
11392
+ }
11393
+ function applyPersistedSettings(state) {
11394
+ if (!scopeOmitDirCliOverride && Array.isArray(state.scopeOmitDirs) && state.scopeOmitDirs.length > 0) {
11395
+ scopeOmitDirNames = state.scopeOmitDirs;
11396
+ } else if (!scopeOmitDirCliOverride) {
11397
+ scopeOmitDirNames = DEFAULT_WORKTREE_OMIT_DIR_NAMES;
11398
+ }
11399
+ if (Array.isArray(state.scopeExcludeNames) && state.scopeExcludeNames.length > 0) {
11400
+ scopeExcludeNames = state.scopeExcludeNames;
11401
+ } else {
11402
+ scopeExcludeNames = DEFAULT_EXCLUDE_NAMES;
11403
+ }
11404
+ uploadEnabled = state.uploadEnabled !== false;
11231
11405
  }
11232
11406
  function json2(data, init = {}) {
11233
11407
  return new Response(JSON.stringify(data), {
@@ -11529,45 +11703,6 @@ function parseScopeExcludeNamesQuery(value) {
11529
11703
  }
11530
11704
  return normalizeScopeExcludeNames(names);
11531
11705
  }
11532
- function loadProjectConfig() {
11533
- const full = join13(cwd, ".code-viewer.json");
11534
- if (!existsSync6(full))
11535
- return null;
11536
- let realCwd;
11537
- let realConfig;
11538
- try {
11539
- realCwd = realpathSync4(cwd);
11540
- realConfig = realpathSync4(full);
11541
- } catch {
11542
- return null;
11543
- }
11544
- if (dirname3(realConfig) !== realCwd || basename3(realConfig) !== ".code-viewer.json")
11545
- return null;
11546
- try {
11547
- const parsed = JSON.parse(readFileSync4(realConfig, "utf8"));
11548
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && "version" in parsed && parsed.version !== 1)
11549
- return null;
11550
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
11551
- } catch {
11552
- return null;
11553
- }
11554
- }
11555
- function loadProjectConfigUploadDisabled() {
11556
- const config = loadProjectConfig();
11557
- return config?.upload?.enabled === false;
11558
- }
11559
- function loadProjectConfigScopeOmitDirs() {
11560
- const config = loadProjectConfig();
11561
- if (!config?.scope || !Array.isArray(config.scope.omitDirs))
11562
- return null;
11563
- return normalizeScopeOmitDirNames(config.scope.omitDirs);
11564
- }
11565
- function loadProjectConfigScopeExcludeNames() {
11566
- const config = loadProjectConfig();
11567
- if (!config?.scope || !Array.isArray(config.scope.excludeNames))
11568
- return null;
11569
- return normalizeScopeExcludeNames(config.scope.excludeNames);
11570
- }
11571
11706
  function scopeOmitDirNamesFromQuery(url) {
11572
11707
  if (!url.searchParams.has("omit_dirs"))
11573
11708
  return scopeOmitDirNames;
@@ -11735,7 +11870,7 @@ function handleTree(url) {
11735
11870
  branch: currentBranch(cwd) || undefined,
11736
11871
  entries: recursive ? entries : entries.map((entry) => attachTreeEntryMetadata(target, entry)),
11737
11872
  readme: readReadme(target, path),
11738
- upload_enabled: !uploadDisabledByConfig && (target === "worktree" || target === "")
11873
+ upload_enabled: uploadEnabled && (target === "worktree" || target === "")
11739
11874
  });
11740
11875
  }
11741
11876
  function handleSettings() {
@@ -12297,8 +12432,8 @@ function uploadOpenFlags() {
12297
12432
  return constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW || 0);
12298
12433
  }
12299
12434
  async function handleUploadFiles(req) {
12300
- if (uploadDisabledByConfig)
12301
- return text("upload disabled by project config", 403);
12435
+ if (!uploadEnabled)
12436
+ return text("upload disabled by viewer settings", 403);
12302
12437
  if (req.method !== "POST")
12303
12438
  return text("method not allowed", 405);
12304
12439
  if (!sideEffectRequestAllowed(req))
@@ -12851,9 +12986,6 @@ async function handleAnnotations(req) {
12851
12986
  }
12852
12987
  return text("invalid action", 400);
12853
12988
  }
12854
- function isCodeViewerInternalPath(path) {
12855
- return path.split(/[\\/]+/).some((part) => part.toLowerCase() === ".code-viewer");
12856
- }
12857
12989
  function sendSse(event, data = "tick") {
12858
12990
  const payload = enc.encode(`event: ${event}
12859
12991
  data: ${data}
@@ -12901,7 +13033,7 @@ async function shutdown(exitCode = 0) {
12901
13033
  }
12902
13034
  process.exit(exitCode);
12903
13035
  }
12904
- 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, uploadDisabledByConfig = false, rgAvailableCache = null, enc, sseClients, sseKeepalives, fileCache, metaCache, fileListCache, lineIndexCache, blobLineIndexCache, blobBytesCache, blobLineCacheBytes = 0, server, worktreeWatch = null, shuttingDown = false;
13036
+ 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, uploadEnabled = true, rgAvailableCache = null, enc, sseClients, sseKeepalives, fileCache, metaCache, fileListCache, lineIndexCache, blobLineIndexCache, blobBytesCache, blobLineCacheBytes = 0, isCodeViewerInternalPath, watchLimitReached = null, server, worktreeWatch = null, shuttingDown = false;
12905
13037
  var init_preview = __esm(async () => {
12906
13038
  init_routes();
12907
13039
  init_annotations();
@@ -12913,6 +13045,7 @@ var init_preview = __esm(async () => {
12913
13045
  init_runtime();
12914
13046
  init_search();
12915
13047
  init_server_registry();
13048
+ init_state_store();
12916
13049
  init_worktree_watcher();
12917
13050
  WEB_ROOT = join13(ROOT, "web");
12918
13051
  VERSION = JSON.parse(readFileSync4(join13(ROOT, "package.json"), "utf8")).version;
@@ -12972,7 +13105,9 @@ var init_preview = __esm(async () => {
12972
13105
  lineIndexCache = new Map;
12973
13106
  blobLineIndexCache = new Map;
12974
13107
  blobBytesCache = new Map;
13108
+ isCodeViewerInternalPath = isToolInternalPath;
12975
13109
  parseCli();
13110
+ applyPersistedSettings(await loadAppSettingsState(cwd));
12976
13111
  server = await startServer({
12977
13112
  hostname: "127.0.0.1",
12978
13113
  port: listenPort,
@@ -13021,7 +13156,7 @@ var init_preview = __esm(async () => {
13021
13156
  }
13022
13157
  if (url.pathname.startsWith("/_state/")) {
13023
13158
  const { handleStateRoute: handleStateRoute2 } = await Promise.resolve().then(() => (init_state_route(), exports_state_route));
13024
- const stateResponse = await handleStateRoute2(req, url, cwd, sideEffectRequestAllowed);
13159
+ const stateResponse = await handleStateRoute2(req, url, cwd, sideEffectRequestAllowed, { onSettingsChange: applyPersistedSettings });
13025
13160
  if (stateResponse)
13026
13161
  return stateResponse;
13027
13162
  }
@@ -13046,6 +13181,12 @@ var init_preview = __esm(async () => {
13046
13181
  data: ok
13047
13182
 
13048
13183
  `));
13184
+ if (watchLimitReached !== null) {
13185
+ controller.enqueue(enc.encode(`event: watch-limit
13186
+ data: ${watchLimitReached}
13187
+
13188
+ `));
13189
+ }
13049
13190
  keepalive = setInterval(() => {
13050
13191
  try {
13051
13192
  controller.enqueue(enc.encode(`: ping
@@ -13115,6 +13256,10 @@ data: ok
13115
13256
  initialScanMode: "async",
13116
13257
  maxWatchedDirectories: worktreeWatchDirectoryLimitFromEnv(),
13117
13258
  onUpdate: triggerUpdate,
13259
+ onWatchLimit: (limit) => {
13260
+ watchLimitReached = limit;
13261
+ sendSse("watch-limit", String(limit));
13262
+ },
13118
13263
  onError: (error) => {
13119
13264
  const message = error instanceof Error ? error.message : String(error);
13120
13265
  console.warn(`code-viewer worktree watch skipped: ${message}`);