reasonix 0.16.1 → 0.17.1
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/dashboard/app.css +159 -6
- package/dashboard/app.js +249 -23
- package/dist/cli/index.js +537 -280
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.js +89 -63
- package/dist/index.js.map +1 -1
- package/package.json +113 -110
package/dashboard/app.css
CHANGED
|
@@ -665,7 +665,8 @@ button:disabled {
|
|
|
665
665
|
}
|
|
666
666
|
|
|
667
667
|
input[type="text"],
|
|
668
|
-
input[type="search"]
|
|
668
|
+
input[type="search"],
|
|
669
|
+
input[type="number"] {
|
|
669
670
|
background: var(--bg-2);
|
|
670
671
|
border: 1px solid var(--border);
|
|
671
672
|
color: var(--fg-0);
|
|
@@ -678,6 +679,7 @@ input[type="search"] {
|
|
|
678
679
|
|
|
679
680
|
input[type="text"]:focus,
|
|
680
681
|
input[type="search"]:focus,
|
|
682
|
+
input[type="number"]:focus,
|
|
681
683
|
input[type="password"]:focus {
|
|
682
684
|
border-color: var(--primary);
|
|
683
685
|
outline: none;
|
|
@@ -712,7 +714,15 @@ select:focus {
|
|
|
712
714
|
textarea {
|
|
713
715
|
background: var(--bg-2);
|
|
714
716
|
color: var(--fg-0);
|
|
717
|
+
border: 1px solid var(--border);
|
|
718
|
+
border-radius: var(--radius-sm);
|
|
719
|
+
padding: 8px 10px;
|
|
715
720
|
font-family: var(--mono);
|
|
721
|
+
font-size: 12px;
|
|
722
|
+
line-height: 1.5;
|
|
723
|
+
resize: vertical;
|
|
724
|
+
width: 100%;
|
|
725
|
+
box-sizing: border-box;
|
|
716
726
|
}
|
|
717
727
|
textarea:focus {
|
|
718
728
|
border-color: var(--primary);
|
|
@@ -2303,19 +2313,25 @@ textarea:focus {
|
|
|
2303
2313
|
}
|
|
2304
2314
|
|
|
2305
2315
|
/* Split view — left half source (CodeMirror), right half preview. */
|
|
2306
|
-
.editor-
|
|
2316
|
+
.editor-stage {
|
|
2307
2317
|
flex: 1;
|
|
2308
2318
|
display: flex;
|
|
2309
2319
|
min-height: 0;
|
|
2310
2320
|
overflow: hidden;
|
|
2311
2321
|
}
|
|
2312
|
-
.editor-
|
|
2322
|
+
.editor-stage > .editor-host,
|
|
2323
|
+
.editor-stage > .editor-md-preview {
|
|
2313
2324
|
flex: 1;
|
|
2314
2325
|
min-width: 0;
|
|
2315
|
-
|
|
2316
|
-
|
|
2326
|
+
min-height: 0;
|
|
2327
|
+
}
|
|
2328
|
+
.editor-stage[data-mode="edit"] > .editor-md-preview {
|
|
2329
|
+
display: none;
|
|
2330
|
+
}
|
|
2331
|
+
.editor-stage[data-mode="preview"] > .editor-host {
|
|
2332
|
+
display: none;
|
|
2317
2333
|
}
|
|
2318
|
-
.editor-split .editor-
|
|
2334
|
+
.editor-stage[data-mode="split"] > .editor-host + .editor-md-preview {
|
|
2319
2335
|
border-left: 1px solid #181a1f;
|
|
2320
2336
|
}
|
|
2321
2337
|
|
|
@@ -2538,3 +2554,140 @@ code,
|
|
|
2538
2554
|
border: 1px dashed var(--border);
|
|
2539
2555
|
border-radius: var(--radius-md);
|
|
2540
2556
|
}
|
|
2557
|
+
|
|
2558
|
+
/* ---------- Excludes settings (Semantic tab) ---------- */
|
|
2559
|
+
|
|
2560
|
+
.excludes-toggle {
|
|
2561
|
+
margin: 24px 0 12px;
|
|
2562
|
+
display: flex;
|
|
2563
|
+
align-items: baseline;
|
|
2564
|
+
gap: 10px;
|
|
2565
|
+
cursor: pointer;
|
|
2566
|
+
user-select: none;
|
|
2567
|
+
}
|
|
2568
|
+
.excludes-toggle:hover {
|
|
2569
|
+
color: var(--primary);
|
|
2570
|
+
}
|
|
2571
|
+
.excludes-toggle .caret {
|
|
2572
|
+
font-size: 11px;
|
|
2573
|
+
color: var(--fg-3);
|
|
2574
|
+
width: 10px;
|
|
2575
|
+
display: inline-block;
|
|
2576
|
+
}
|
|
2577
|
+
.excludes-toggle .label {
|
|
2578
|
+
font-size: 11px;
|
|
2579
|
+
letter-spacing: 0.1em;
|
|
2580
|
+
text-transform: uppercase;
|
|
2581
|
+
color: var(--fg-3);
|
|
2582
|
+
}
|
|
2583
|
+
.excludes-toggle:hover .label,
|
|
2584
|
+
.excludes-toggle:hover .caret {
|
|
2585
|
+
color: var(--primary);
|
|
2586
|
+
}
|
|
2587
|
+
.excludes-toggle .hint {
|
|
2588
|
+
font-size: 12px;
|
|
2589
|
+
color: var(--fg-3);
|
|
2590
|
+
font-weight: normal;
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
.excludes-card {
|
|
2594
|
+
font-size: 13px;
|
|
2595
|
+
display: flex;
|
|
2596
|
+
flex-direction: column;
|
|
2597
|
+
gap: 12px;
|
|
2598
|
+
}
|
|
2599
|
+
.excludes-card .lead {
|
|
2600
|
+
color: var(--fg-2);
|
|
2601
|
+
font-size: 12px;
|
|
2602
|
+
line-height: 1.5;
|
|
2603
|
+
}
|
|
2604
|
+
.excludes-grid {
|
|
2605
|
+
display: grid;
|
|
2606
|
+
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
2607
|
+
gap: 14px;
|
|
2608
|
+
}
|
|
2609
|
+
.excludes-field label {
|
|
2610
|
+
display: block;
|
|
2611
|
+
font-size: 12px;
|
|
2612
|
+
color: var(--fg-2);
|
|
2613
|
+
margin-bottom: 4px;
|
|
2614
|
+
font-weight: 500;
|
|
2615
|
+
}
|
|
2616
|
+
.excludes-field textarea {
|
|
2617
|
+
min-height: 90px;
|
|
2618
|
+
}
|
|
2619
|
+
.excludes-options {
|
|
2620
|
+
display: flex;
|
|
2621
|
+
flex-wrap: wrap;
|
|
2622
|
+
gap: 18px;
|
|
2623
|
+
align-items: center;
|
|
2624
|
+
padding-top: 4px;
|
|
2625
|
+
font-size: 12px;
|
|
2626
|
+
color: var(--fg-1);
|
|
2627
|
+
}
|
|
2628
|
+
.excludes-options label {
|
|
2629
|
+
display: inline-flex;
|
|
2630
|
+
align-items: center;
|
|
2631
|
+
gap: 6px;
|
|
2632
|
+
}
|
|
2633
|
+
.excludes-options input[type="number"] {
|
|
2634
|
+
width: 110px;
|
|
2635
|
+
}
|
|
2636
|
+
.excludes-actions {
|
|
2637
|
+
display: flex;
|
|
2638
|
+
gap: 10px;
|
|
2639
|
+
flex-wrap: wrap;
|
|
2640
|
+
margin-top: 4px;
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
.excludes-preview {
|
|
2644
|
+
margin-top: 4px;
|
|
2645
|
+
padding-top: 12px;
|
|
2646
|
+
border-top: 1px solid var(--border);
|
|
2647
|
+
font-size: 12px;
|
|
2648
|
+
display: flex;
|
|
2649
|
+
flex-direction: column;
|
|
2650
|
+
gap: 8px;
|
|
2651
|
+
}
|
|
2652
|
+
.excludes-preview .summary {
|
|
2653
|
+
font-size: 13px;
|
|
2654
|
+
color: var(--fg-1);
|
|
2655
|
+
}
|
|
2656
|
+
.excludes-preview details {
|
|
2657
|
+
background: var(--bg-2);
|
|
2658
|
+
border: 1px solid var(--border);
|
|
2659
|
+
border-radius: var(--radius-sm);
|
|
2660
|
+
padding: 6px 10px;
|
|
2661
|
+
}
|
|
2662
|
+
.excludes-preview details[open] {
|
|
2663
|
+
background: var(--bg-3);
|
|
2664
|
+
}
|
|
2665
|
+
.excludes-preview summary {
|
|
2666
|
+
cursor: pointer;
|
|
2667
|
+
color: var(--fg-1);
|
|
2668
|
+
font-weight: 500;
|
|
2669
|
+
}
|
|
2670
|
+
.excludes-preview summary:hover {
|
|
2671
|
+
color: var(--primary);
|
|
2672
|
+
}
|
|
2673
|
+
.excludes-preview ul {
|
|
2674
|
+
margin: 6px 0 0 16px;
|
|
2675
|
+
padding: 0;
|
|
2676
|
+
font-size: 11.5px;
|
|
2677
|
+
color: var(--fg-2);
|
|
2678
|
+
}
|
|
2679
|
+
.excludes-preview li {
|
|
2680
|
+
margin: 2px 0;
|
|
2681
|
+
}
|
|
2682
|
+
.excludes-preview li code {
|
|
2683
|
+
font-size: 11.5px;
|
|
2684
|
+
background: transparent;
|
|
2685
|
+
padding: 0;
|
|
2686
|
+
color: var(--fg-1);
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
.skip-buckets {
|
|
2690
|
+
margin-top: 4px;
|
|
2691
|
+
font-size: 12px;
|
|
2692
|
+
color: var(--fg-2);
|
|
2693
|
+
}
|
package/dashboard/app.js
CHANGED
|
@@ -3265,10 +3265,212 @@ function SemanticPanel() {
|
|
|
3265
3265
|
<button disabled=${busy || running || !ready} onClick=${() => start(true)}>Rebuild (wipe + full)</button>
|
|
3266
3266
|
<button disabled=${busy || !running} onClick=${stop}>Stop</button>
|
|
3267
3267
|
</div>
|
|
3268
|
+
|
|
3269
|
+
<${SemanticExcludesCard} />
|
|
3270
|
+
</div>
|
|
3271
|
+
`;
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
function SemanticExcludesCard() {
|
|
3275
|
+
const [data, setData] = useState(null);
|
|
3276
|
+
const [draft, setDraft] = useState(null);
|
|
3277
|
+
const [preview, setPreview] = useState(null);
|
|
3278
|
+
const [busy, setBusy] = useState(false);
|
|
3279
|
+
const [error, setError] = useState(null);
|
|
3280
|
+
const [info, setInfo] = useState(null);
|
|
3281
|
+
const [open, setOpen] = useState(false);
|
|
3282
|
+
|
|
3283
|
+
const load = useCallback(async () => {
|
|
3284
|
+
try {
|
|
3285
|
+
const r = await api("/index-config");
|
|
3286
|
+
setData(r);
|
|
3287
|
+
setDraft(toDraft(r.resolved));
|
|
3288
|
+
} catch (err) {
|
|
3289
|
+
setError(err.message);
|
|
3290
|
+
}
|
|
3291
|
+
}, []);
|
|
3292
|
+
|
|
3293
|
+
useEffect(() => {
|
|
3294
|
+
if (open && !data) load();
|
|
3295
|
+
}, [open, data, load]);
|
|
3296
|
+
|
|
3297
|
+
const reset = useCallback(() => {
|
|
3298
|
+
if (data) setDraft(toDraft(data.defaults));
|
|
3299
|
+
setPreview(null);
|
|
3300
|
+
}, [data]);
|
|
3301
|
+
|
|
3302
|
+
const save = useCallback(async () => {
|
|
3303
|
+
if (!draft) return;
|
|
3304
|
+
setBusy(true);
|
|
3305
|
+
setError(null);
|
|
3306
|
+
setInfo(null);
|
|
3307
|
+
try {
|
|
3308
|
+
const payload = fromDraft(draft);
|
|
3309
|
+
const r = await api("/index-config", { method: "POST", body: payload });
|
|
3310
|
+
setInfo(`saved · ${r.changed.length || 0} fields updated · re-run index to apply`);
|
|
3311
|
+
await load();
|
|
3312
|
+
} catch (err) {
|
|
3313
|
+
setError(err.message);
|
|
3314
|
+
} finally {
|
|
3315
|
+
setBusy(false);
|
|
3316
|
+
}
|
|
3317
|
+
}, [draft, load]);
|
|
3318
|
+
|
|
3319
|
+
const runPreview = useCallback(async () => {
|
|
3320
|
+
if (!draft) return;
|
|
3321
|
+
setBusy(true);
|
|
3322
|
+
setError(null);
|
|
3323
|
+
setInfo("running dry walk against project root…");
|
|
3324
|
+
try {
|
|
3325
|
+
const payload = fromDraft(draft);
|
|
3326
|
+
const r = await api("/index-config/preview", { method: "POST", body: payload });
|
|
3327
|
+
setPreview(r);
|
|
3328
|
+
setInfo(null);
|
|
3329
|
+
} catch (err) {
|
|
3330
|
+
setError(err.message);
|
|
3331
|
+
setInfo(null);
|
|
3332
|
+
} finally {
|
|
3333
|
+
setBusy(false);
|
|
3334
|
+
}
|
|
3335
|
+
}, [draft]);
|
|
3336
|
+
|
|
3337
|
+
return html`
|
|
3338
|
+
<div class="excludes-toggle" onClick=${() => setOpen(!open)}>
|
|
3339
|
+
<span class="caret">${open ? "▼" : "▶"}</span>
|
|
3340
|
+
<span class="label">Excludes</span>
|
|
3341
|
+
<span class="hint">config-driven skip rules applied during indexing</span>
|
|
3342
|
+
</div>
|
|
3343
|
+
${
|
|
3344
|
+
!open
|
|
3345
|
+
? null
|
|
3346
|
+
: !draft
|
|
3347
|
+
? html`<div class="empty">loading…</div>`
|
|
3348
|
+
: html`
|
|
3349
|
+
<div class="card excludes-card">
|
|
3350
|
+
${info ? html`<div class="notice">${info}</div>` : null}
|
|
3351
|
+
${error ? html`<div class="notice err">${error}</div>` : null}
|
|
3352
|
+
<div class="lead">
|
|
3353
|
+
One value per line. Dirs / files match by basename. Patterns use picomatch syntax (e.g. <code>**/*.generated.ts</code>, <code>vendor/**</code>, <code>!keep-me</code>).
|
|
3354
|
+
</div>
|
|
3355
|
+
<div class="excludes-grid">
|
|
3356
|
+
<${ExcludesField} label="Exclude dirs" value=${draft.excludeDirs} onChange=${(v) => setDraft({ ...draft, excludeDirs: v })} />
|
|
3357
|
+
<${ExcludesField} label="Exclude files" value=${draft.excludeFiles} onChange=${(v) => setDraft({ ...draft, excludeFiles: v })} />
|
|
3358
|
+
<${ExcludesField} label="Exclude extensions" value=${draft.excludeExts} onChange=${(v) => setDraft({ ...draft, excludeExts: v })} />
|
|
3359
|
+
<${ExcludesField} label="Exclude patterns (glob)" value=${draft.excludePatterns} onChange=${(v) => setDraft({ ...draft, excludePatterns: v })} />
|
|
3360
|
+
</div>
|
|
3361
|
+
<div class="excludes-options">
|
|
3362
|
+
<label>
|
|
3363
|
+
<input type="checkbox" checked=${draft.respectGitignore} onChange=${(e) => setDraft({ ...draft, respectGitignore: e.target.checked })} />
|
|
3364
|
+
Respect <code>.gitignore</code>
|
|
3365
|
+
</label>
|
|
3366
|
+
<label>
|
|
3367
|
+
Max file size
|
|
3368
|
+
<input type="number" min="1024" step="1024" value=${draft.maxFileBytes} onChange=${(e) => setDraft({ ...draft, maxFileBytes: Number(e.target.value) || 0 })} />
|
|
3369
|
+
<span class="muted">bytes</span>
|
|
3370
|
+
</label>
|
|
3371
|
+
</div>
|
|
3372
|
+
<div class="excludes-actions">
|
|
3373
|
+
<button class="primary" disabled=${busy} onClick=${save}>Save</button>
|
|
3374
|
+
<button disabled=${busy} onClick=${runPreview}>Preview (dry-walk)</button>
|
|
3375
|
+
<button disabled=${busy} onClick=${reset}>Reset to defaults</button>
|
|
3376
|
+
</div>
|
|
3377
|
+
${preview ? html`<${ExcludesPreview} preview=${preview} />` : null}
|
|
3378
|
+
</div>
|
|
3379
|
+
`
|
|
3380
|
+
}
|
|
3381
|
+
`;
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
function ExcludesPreview({ preview }) {
|
|
3385
|
+
const buckets = preview.skipBuckets || {};
|
|
3386
|
+
const samples = preview.skipSamples || {};
|
|
3387
|
+
const totalSkipped = Object.values(buckets).reduce((a, b) => a + (b || 0), 0);
|
|
3388
|
+
const reasons = [
|
|
3389
|
+
"gitignore",
|
|
3390
|
+
"pattern",
|
|
3391
|
+
"defaultDir",
|
|
3392
|
+
"defaultFile",
|
|
3393
|
+
"binaryExt",
|
|
3394
|
+
"binaryContent",
|
|
3395
|
+
"tooLarge",
|
|
3396
|
+
"readError",
|
|
3397
|
+
].filter((k) => (buckets[k] || 0) > 0);
|
|
3398
|
+
return html`
|
|
3399
|
+
<div class="excludes-preview">
|
|
3400
|
+
<div class="summary">
|
|
3401
|
+
Preview — would index <strong>${preview.filesIncluded}</strong> file(s), skip <strong>${totalSkipped}</strong>
|
|
3402
|
+
</div>
|
|
3403
|
+
${
|
|
3404
|
+
reasons.length === 0
|
|
3405
|
+
? html`<div class="muted">nothing skipped — all walked files would be indexed.</div>`
|
|
3406
|
+
: reasons.map(
|
|
3407
|
+
(r) => html`
|
|
3408
|
+
<details>
|
|
3409
|
+
<summary><strong>${r}: ${buckets[r]}</strong></summary>
|
|
3410
|
+
<ul>
|
|
3411
|
+
${(samples[r] || []).map((p) => html`<li><code>${p}</code></li>`)}
|
|
3412
|
+
${
|
|
3413
|
+
(buckets[r] || 0) > (samples[r] || []).length
|
|
3414
|
+
? html`<li class="muted">…${buckets[r] - samples[r].length} more</li>`
|
|
3415
|
+
: null
|
|
3416
|
+
}
|
|
3417
|
+
</ul>
|
|
3418
|
+
</details>
|
|
3419
|
+
`,
|
|
3420
|
+
)
|
|
3421
|
+
}
|
|
3422
|
+
${
|
|
3423
|
+
preview.sampleIncluded?.length
|
|
3424
|
+
? html`
|
|
3425
|
+
<details>
|
|
3426
|
+
<summary>first ${preview.sampleIncluded.length} included file(s)</summary>
|
|
3427
|
+
<ul>
|
|
3428
|
+
${preview.sampleIncluded.map((p) => html`<li><code>${p}</code></li>`)}
|
|
3429
|
+
</ul>
|
|
3430
|
+
</details>
|
|
3431
|
+
`
|
|
3432
|
+
: null
|
|
3433
|
+
}
|
|
3268
3434
|
</div>
|
|
3269
3435
|
`;
|
|
3270
3436
|
}
|
|
3271
3437
|
|
|
3438
|
+
function ExcludesField({ label, value, onChange }) {
|
|
3439
|
+
return html`
|
|
3440
|
+
<div class="excludes-field">
|
|
3441
|
+
<label>${label}</label>
|
|
3442
|
+
<textarea rows="5" value=${value} onChange=${(e) => onChange(e.target.value)}></textarea>
|
|
3443
|
+
</div>
|
|
3444
|
+
`;
|
|
3445
|
+
}
|
|
3446
|
+
|
|
3447
|
+
function toDraft(c) {
|
|
3448
|
+
return {
|
|
3449
|
+
excludeDirs: (c.excludeDirs ?? []).join("\n"),
|
|
3450
|
+
excludeFiles: (c.excludeFiles ?? []).join("\n"),
|
|
3451
|
+
excludeExts: (c.excludeExts ?? []).join("\n"),
|
|
3452
|
+
excludePatterns: (c.excludePatterns ?? []).join("\n"),
|
|
3453
|
+
respectGitignore: c.respectGitignore !== false,
|
|
3454
|
+
maxFileBytes: c.maxFileBytes ?? 262144,
|
|
3455
|
+
};
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
function fromDraft(d) {
|
|
3459
|
+
const lines = (s) =>
|
|
3460
|
+
s
|
|
3461
|
+
.split(/\r?\n/)
|
|
3462
|
+
.map((x) => x.trim())
|
|
3463
|
+
.filter((x) => x.length > 0);
|
|
3464
|
+
return {
|
|
3465
|
+
excludeDirs: lines(d.excludeDirs),
|
|
3466
|
+
excludeFiles: lines(d.excludeFiles),
|
|
3467
|
+
excludeExts: lines(d.excludeExts),
|
|
3468
|
+
excludePatterns: lines(d.excludePatterns),
|
|
3469
|
+
respectGitignore: !!d.respectGitignore,
|
|
3470
|
+
maxFileBytes: d.maxFileBytes,
|
|
3471
|
+
};
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3272
3474
|
function SemanticJobView({ job, running }) {
|
|
3273
3475
|
const phaseLabel =
|
|
3274
3476
|
{
|
|
@@ -3321,10 +3523,34 @@ function SemanticJobView({ job, running }) {
|
|
|
3321
3523
|
} · ${(job.result.durationMs / 1000).toFixed(1)}s</div>`
|
|
3322
3524
|
: null
|
|
3323
3525
|
}
|
|
3526
|
+
${
|
|
3527
|
+
job.result?.skipBuckets
|
|
3528
|
+
? html`<${SkipBucketsView} buckets=${job.result.skipBuckets} />`
|
|
3529
|
+
: null
|
|
3530
|
+
}
|
|
3324
3531
|
</div>
|
|
3325
3532
|
`;
|
|
3326
3533
|
}
|
|
3327
3534
|
|
|
3535
|
+
function SkipBucketsView({ buckets }) {
|
|
3536
|
+
const order = [
|
|
3537
|
+
["gitignore", "gitignore"],
|
|
3538
|
+
["pattern", "pattern"],
|
|
3539
|
+
["defaultDir", "defaultDir"],
|
|
3540
|
+
["defaultFile", "defaultFile"],
|
|
3541
|
+
["binaryExt", "binaryExt"],
|
|
3542
|
+
["binaryContent", "binaryContent"],
|
|
3543
|
+
["tooLarge", "tooLarge"],
|
|
3544
|
+
["readError", "readError"],
|
|
3545
|
+
];
|
|
3546
|
+
const total = order.reduce((a, [k]) => a + (buckets[k] || 0), 0);
|
|
3547
|
+
if (total === 0) return null;
|
|
3548
|
+
const parts = order
|
|
3549
|
+
.filter(([k]) => (buckets[k] || 0) > 0)
|
|
3550
|
+
.map(([k, label]) => `${label}: ${buckets[k]}`);
|
|
3551
|
+
return html`<div><span class="kv-key">skipped</span>${total} files <span class="muted">(${parts.join(", ")})</span></div>`;
|
|
3552
|
+
}
|
|
3553
|
+
|
|
3328
3554
|
function McpPanel() {
|
|
3329
3555
|
const [data, setData] = useState(null);
|
|
3330
3556
|
const [specs, setSpecs] = useState(null);
|
|
@@ -3753,7 +3979,13 @@ function EditorPanel({ onClose } = {}) {
|
|
|
3753
3979
|
viewRef.current = null;
|
|
3754
3980
|
}
|
|
3755
3981
|
};
|
|
3756
|
-
}, [cmReady, activeIdx, tabs[activeIdx]?.path
|
|
3982
|
+
}, [cmReady, activeIdx, tabs[activeIdx]?.path]);
|
|
3983
|
+
|
|
3984
|
+
// Becoming visible after display:none — CM6 measures lazily, force it.
|
|
3985
|
+
useEffect(() => {
|
|
3986
|
+
if (viewMode === "preview") return;
|
|
3987
|
+
viewRef.current?.requestMeasure?.();
|
|
3988
|
+
}, [viewMode]);
|
|
3757
3989
|
|
|
3758
3990
|
const closeTab = useCallback((idx) => {
|
|
3759
3991
|
const tab = tabsRef.current[idx];
|
|
@@ -3994,28 +4226,22 @@ function EditorPanel({ onClose } = {}) {
|
|
|
3994
4226
|
${(() => {
|
|
3995
4227
|
const isMd = langFromPath(tab.path) === "markdown";
|
|
3996
4228
|
const mode = isMd ? viewMode : "edit";
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
}}
|
|
4014
|
-
></div>
|
|
4015
|
-
</div>
|
|
4016
|
-
`;
|
|
4017
|
-
}
|
|
4018
|
-
return html`<div ref=${editorContainerRef} class="editor-host"></div>`;
|
|
4229
|
+
// Stable DOM across mode toggles — CM-managed children + Preact reconciliation don't mix when the host moves.
|
|
4230
|
+
return html`
|
|
4231
|
+
<div class="editor-stage" data-mode=${mode}>
|
|
4232
|
+
<div ref=${editorContainerRef} class="editor-host"></div>
|
|
4233
|
+
${
|
|
4234
|
+
isMd
|
|
4235
|
+
? html`<div
|
|
4236
|
+
class="editor-md-preview md"
|
|
4237
|
+
dangerouslySetInnerHTML=${{
|
|
4238
|
+
__html: previewMarked.parse(tab.content ?? ""),
|
|
4239
|
+
}}
|
|
4240
|
+
></div>`
|
|
4241
|
+
: null
|
|
4242
|
+
}
|
|
4243
|
+
</div>
|
|
4244
|
+
`;
|
|
4019
4245
|
})()}
|
|
4020
4246
|
`
|
|
4021
4247
|
: html`
|