claude-code-memory-explorer 0.1.0 → 0.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-memory-explorer",
3
- "version": "0.1.0",
3
+ "version": "0.2.2",
4
4
  "description": "Memory explorer dashboard for Claude Code",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -22,5 +22,30 @@
22
22
  "devDependencies": {
23
23
  "husky": "^9.0.0"
24
24
  },
25
- "license": "MIT"
25
+ "keywords": [
26
+ "claude",
27
+ "claude-code",
28
+ "memory",
29
+ "dashboard",
30
+ "anthropic",
31
+ "ai",
32
+ "cli",
33
+ "explorer"
34
+ ],
35
+ "homepage": "https://github.com/NikiforovAll/claude-code-memory",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/NikiforovAll/claude-code-memory.git"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/NikiforovAll/claude-code-memory/issues"
42
+ },
43
+ "author": "NikiforovAll",
44
+ "license": "MIT",
45
+ "files": [
46
+ "server.js",
47
+ "public/",
48
+ "README.md",
49
+ "LICENSE"
50
+ ]
26
51
  }
package/public/app.js CHANGED
@@ -59,11 +59,13 @@ function highlightSource(text, fileName) {
59
59
 
60
60
  function linkifyImports(text, sourceId) {
61
61
  if (!stackData.length) return { text, placeholders: [] };
62
- const byName = Object.fromEntries(stackData.map(c => [c.name, c]));
62
+ const byName = Object.fromEntries(stackData.map((c) => [c.name, c]));
63
63
  const placeholders = [];
64
64
  const ph = (child, display) => {
65
65
  const token = `\x00LINK${placeholders.length}\x00`;
66
- placeholders.push(`<a class="inline-import" href="#" onclick="selectFile('${escJs(child.id)}');return false" title="${esc(child.path)}">${esc(display)}</a>`);
66
+ placeholders.push(
67
+ `<a class="inline-import" href="#" onclick="selectFile('${escJs(child.id)}');return false" title="${esc(child.path)}">${esc(display)}</a>`,
68
+ );
67
69
  return token;
68
70
  };
69
71
  // Replace @path refs with placeholders
@@ -293,7 +295,9 @@ function getTreeIndex() {
293
295
  return treeIndex;
294
296
  }
295
297
 
296
- function invalidateTreeIndex() { treeIndex = null; }
298
+ function invalidateTreeIndex() {
299
+ treeIndex = null;
300
+ }
297
301
 
298
302
  function renderTree() {
299
303
  const container = document.getElementById('treeContent');
@@ -376,9 +380,7 @@ function navigateGroup(direction) {
376
380
  if (!activeScopes.length) return;
377
381
 
378
382
  const current = stackData.find((s) => s.id === selectedFileId);
379
- const currentScope = current?.parentId
380
- ? stackData.find((s) => s.id === current.parentId)?.scope
381
- : current?.scope;
383
+ const currentScope = current?.parentId ? stackData.find((s) => s.id === current.parentId)?.scope : current?.scope;
382
384
  let scopeIdx = activeScopes.indexOf(currentScope);
383
385
  if (scopeIdx === -1) {
384
386
  scopeIdx = direction > 0 ? 0 : activeScopes.length - 1;
@@ -435,7 +437,7 @@ async function renderPreview() {
435
437
  html += '</div>';
436
438
 
437
439
  // Imports — only show children (files that have this source as parent)
438
- const children = stackData.filter(s => s.parentId === source.id);
440
+ const children = stackData.filter((s) => s.parentId === source.id);
439
441
  const unresolved = source.unresolvedImports || [];
440
442
  if (children.length || unresolved.length) {
441
443
  html += '<div class="preview-imports">';
@@ -516,11 +518,15 @@ function renderBudget() {
516
518
  document.getElementById('statAlways').textContent = summaryData.alwaysLoaded;
517
519
 
518
520
  // Budget text
519
- document.getElementById('budgetText').textContent = `${summaryData.totalLines.toLocaleString()} lines / ${formatBytes(summaryData.totalBytes)}`;
521
+ document.getElementById('budgetText').textContent =
522
+ `${summaryData.totalLines.toLocaleString()} lines / ${formatBytes(summaryData.totalBytes)}`;
520
523
 
521
524
  // Budget segments — proportional by lines per scope
522
525
  const segContainer = document.getElementById('budgetSegments');
523
- if (!stackData.length) { segContainer.innerHTML = ''; return; }
526
+ if (!stackData.length) {
527
+ segContainer.innerHTML = '';
528
+ return;
529
+ }
524
530
 
525
531
  const scopeTotals = {};
526
532
  for (const s of stackData) {
@@ -543,10 +549,7 @@ function renderBudget() {
543
549
 
544
550
  async function loadData() {
545
551
  try {
546
- [stackData, summaryData] = await Promise.all([
547
- fetchJSON('/api/stack'),
548
- fetchJSON('/api/summary'),
549
- ]);
552
+ [stackData, summaryData] = await Promise.all([fetchJSON('/api/stack'), fetchJSON('/api/summary')]);
550
553
  invalidateTreeIndex();
551
554
  renderTree();
552
555
  renderBudget();
@@ -556,7 +559,10 @@ async function loadData() {
556
559
  const auto = proj || user;
557
560
  if (auto) selectedFileId = auto.id;
558
561
  }
559
- if (selectedFileId) { renderTree(); renderPreview(); }
562
+ if (selectedFileId) {
563
+ renderTree();
564
+ renderPreview();
565
+ }
560
566
  } catch (err) {
561
567
  showToast('Failed to load: ' + err.message, 'error');
562
568
  }
@@ -609,10 +615,22 @@ document.addEventListener('keydown', (e) => {
609
615
  e.preventDefault();
610
616
  changeProject();
611
617
  }
612
- if (e.key === 'j' || e.key === 'ArrowDown') { e.preventDefault(); navigateTree(1); }
613
- if (e.key === 'k' || e.key === 'ArrowUp') { e.preventDefault(); navigateTree(-1); }
614
- if (e.key === 'h' || e.key === 'ArrowLeft') { e.preventDefault(); navigateGroup(-1); }
615
- if (e.key === 'l' || e.key === 'ArrowRight') { e.preventDefault(); navigateGroup(1); }
618
+ if (e.key === 'j' || e.key === 'ArrowDown') {
619
+ e.preventDefault();
620
+ navigateTree(1);
621
+ }
622
+ if (e.key === 'k' || e.key === 'ArrowUp') {
623
+ e.preventDefault();
624
+ navigateTree(-1);
625
+ }
626
+ if (e.key === 'h' || e.key === 'ArrowLeft') {
627
+ e.preventDefault();
628
+ navigateGroup(-1);
629
+ }
630
+ if (e.key === 'l' || e.key === 'ArrowRight') {
631
+ e.preventDefault();
632
+ navigateGroup(1);
633
+ }
616
634
  if (e.key === 'Enter' && selectedFileId) renderPreview();
617
635
  if (e.key === 'e' && selectedFileId) {
618
636
  const s = stackData.find((x) => x.id === selectedFileId);
@@ -624,13 +642,27 @@ document.addEventListener('keydown', (e) => {
624
642
 
625
643
  // #region HUB_INTEGRATION
626
644
 
627
- async function detectHub() {
628
- try {
629
- const res = await fetch('/hub-config');
630
- if (res.ok) window.__HUB__ = true;
631
- } catch {
632
- /* not in hub */
633
- }
645
+ (async function initHub() {
646
+ const cfg = await fetch('/hub-config')
647
+ .then((r) => r.json())
648
+ .catch(() => ({}));
649
+ if (!cfg.enabled) return;
650
+ window.__HUB__ = cfg;
651
+ document.addEventListener('keydown', (e) => {
652
+ if (e.ctrlKey && e.altKey && (e.key === 'ArrowLeft' || e.key === 'ArrowRight')) {
653
+ e.preventDefault();
654
+ window.parent?.postMessage({ type: 'hub:keydown', key: e.key }, '*');
655
+ }
656
+ if (e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey && /^[1-9]$/.test(e.key)) {
657
+ e.preventDefault();
658
+ window.parent?.postMessage({ type: 'hub:keydown', key: e.key }, '*');
659
+ }
660
+ });
661
+ })();
662
+
663
+ function hubNavigate(app, url) {
664
+ if (!window.__HUB__?.enabled) return;
665
+ window.parent?.postMessage({ type: 'hub:navigate', app, url }, '*');
634
666
  }
635
667
 
636
668
  // #endregion HUB_INTEGRATION
@@ -688,7 +720,6 @@ document.addEventListener('DOMContentLoaded', async () => {
688
720
  loadTheme();
689
721
  initResize();
690
722
  bindModalKeys('projectPathInput', 'projectPickerModal', submitProjectPicker);
691
- await detectHub();
692
723
  // Handle ?project= query param
693
724
  const params = new URLSearchParams(location.search);
694
725
  if (params.has('project')) {
@@ -705,12 +736,21 @@ document.addEventListener('DOMContentLoaded', async () => {
705
736
  const qs = params.toString();
706
737
  history.replaceState(null, '', qs ? `?${qs}` : location.pathname + location.hash);
707
738
  }
708
- await loadProject();
709
- addRecentProject(projectData.path);
739
+ // Retry initial load — server may not be ready yet (e.g. Hub iframe race)
740
+ for (let attempt = 0; attempt < 5; attempt++) {
741
+ try {
742
+ await loadProject();
743
+ break;
744
+ } catch {
745
+ if (attempt < 4) await new Promise((r) => setTimeout(r, 500));
746
+ else showToast('Failed to connect to server', 'error');
747
+ }
748
+ }
749
+ if (projectData) addRecentProject(projectData.path);
710
750
  await loadData();
711
751
  // Restore file selection from hash
712
752
  const hash = decodeURIComponent(location.hash.slice(1));
713
- if (hash && stackData.find(s => s.id === hash)) {
753
+ if (hash && stackData.find((s) => s.id === hash)) {
714
754
  selectedFileId = hash;
715
755
  renderTree();
716
756
  renderPreview();
package/public/index.html CHANGED
@@ -40,6 +40,9 @@
40
40
  <button class="topbar-btn" id="themeBtn" title="Toggle theme (t)" onclick="toggleTheme()">
41
41
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
42
42
  </button>
43
+ <a class="topbar-btn" href="https://github.com/NikiforovAll/claude-code-memory" target="_blank" rel="noopener" title="GitHub">
44
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" stroke="none"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
45
+ </a>
43
46
  </div>
44
47
 
45
48
  <!-- Size bar -->
package/public/style.css CHANGED
@@ -120,7 +120,7 @@ body {
120
120
  cursor: pointer;
121
121
  font-size: 11px;
122
122
  font-family: var(--font-mono);
123
- max-width: 300px;
123
+ white-space: nowrap;
124
124
  }
125
125
  .topbar-project:hover {
126
126
  border-color: var(--accent);
@@ -130,25 +130,34 @@ body {
130
130
  flex: 1;
131
131
  }
132
132
  .topbar-btn {
133
+ display: flex;
134
+ align-items: center;
135
+ gap: 5px;
133
136
  padding: 6px 10px;
134
137
  border-radius: 6px;
135
138
  font-size: 11px;
136
139
  font-weight: 500;
140
+ cursor: pointer;
137
141
  border: 1px solid var(--border);
138
142
  background: var(--bg-elevated);
139
143
  color: var(--text-tertiary);
140
144
  font-family: var(--font-mono);
145
+ transition: all 0.15s ease;
146
+ white-space: nowrap;
141
147
  text-transform: uppercase;
142
148
  letter-spacing: 0.03em;
143
- cursor: pointer;
144
- display: flex;
145
- align-items: center;
146
- gap: 6px;
149
+ text-decoration: none;
147
150
  }
148
151
  .topbar-btn:hover {
149
152
  border-color: var(--accent);
150
153
  color: var(--text-secondary);
151
154
  }
155
+ .topbar-btn svg {
156
+ color: var(--text-muted);
157
+ }
158
+ .topbar-btn.loading svg {
159
+ animation: spin 1s linear infinite;
160
+ }
152
161
  /* #endregion TOPBAR */
153
162
 
154
163
  /* #region BUDGET_BAR */
@@ -288,9 +297,15 @@ body {
288
297
  font-family: var(--font-mono);
289
298
  flex-shrink: 0;
290
299
  }
291
- .tree-child .file-name { opacity: 0.75; }
292
- .tree-conditional .file-name { opacity: 0.55; }
293
- body.light .tree-conditional .file-name { opacity: 0.75; }
300
+ .tree-child .file-name {
301
+ opacity: 0.75;
302
+ }
303
+ .tree-conditional .file-name {
304
+ opacity: 0.55;
305
+ }
306
+ body.light .tree-conditional .file-name {
307
+ opacity: 0.75;
308
+ }
294
309
  /* #endregion TREE */
295
310
 
296
311
  /* #region PREVIEW */
@@ -752,13 +767,13 @@ kbd {
752
767
  body.light {
753
768
  --bg-deep: #e8e6e3;
754
769
  --bg-surface: #f4f3f1;
755
- --bg-elevated: #ffffff;
756
- --bg-hover: #e0ddd8;
757
- --border: #c5c0b8;
770
+ --bg-elevated: #dddbd8;
771
+ --bg-hover: #d2d0cc;
772
+ --border: #a09b94;
758
773
  --text-primary: #0a0a0a;
759
- --text-secondary: #333333;
774
+ --text-secondary: #444444;
760
775
  --text-tertiary: #666666;
761
- --text-muted: #999999;
776
+ --text-muted: #888888;
762
777
  --accent-text: #b85a20;
763
778
  }
764
779
  /* #endregion LIGHT_THEME */
package/server.js CHANGED
@@ -250,6 +250,38 @@ function discoverMemorySources(projectPath) {
250
250
  }
251
251
  }
252
252
 
253
+ // 4b. Scan subdirectories of projectPath for CLAUDE.md (tree-scoped)
254
+ const SKIP_DIRS = new Set(['node_modules', '.git', '.hg', '.svn', 'dist', 'build', 'out', '.next', '.nuxt', 'vendor', '__pycache__', '.venv', 'venv']);
255
+ function walkForClaudeMd(dir, depth) {
256
+ if (depth > 5) return;
257
+ let entries;
258
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
259
+ for (const entry of entries) {
260
+ if (!entry.isDirectory() || SKIP_DIRS.has(entry.name) || entry.name.startsWith('.')) continue;
261
+ const subdir = path.join(dir, entry.name);
262
+ for (const name of ['CLAUDE.md', '.claude/CLAUDE.md']) {
263
+ const filePath = path.join(subdir, name);
264
+ if (seenPaths.has(filePath)) continue;
265
+ const info = fileInfo(filePath);
266
+ if (!info) continue;
267
+ seenPaths.add(filePath);
268
+ const rel = path.relative(projectPath, subdir).replace(/\\/g, '/');
269
+ sources.push({
270
+ id: `project-claude-md-${subdir.replace(/[^a-zA-Z0-9]/g, '-')}`,
271
+ name: `${rel}/${name.includes('/') ? '.claude/CLAUDE.md' : 'CLAUDE.md'}`,
272
+ scope: 'project',
273
+ load: 'tree',
274
+ ...info,
275
+ dir: subdir,
276
+ isProjectRoot: false,
277
+ ...spreadImports(info.path, info.content),
278
+ });
279
+ }
280
+ walkForClaudeMd(subdir, depth + 1);
281
+ }
282
+ }
283
+ walkForClaudeMd(projectPath, 0);
284
+
253
285
  // 5. Project rules (.claude/rules/*.md)
254
286
  const projectRulesDir = path.join(projectPath, '.claude', 'rules');
255
287
  if (fs.existsSync(projectRulesDir)) {
@@ -439,6 +471,8 @@ app.get('/hub-config', (_req, res) => {
439
471
  name: 'Claude Code Memory',
440
472
  icon: 'brain',
441
473
  description: 'Explore Claude Code memory sources',
474
+ enabled: !!process.env.CLAUDE_HUB,
475
+ url: process.env.HUB_URL || null,
442
476
  });
443
477
  });
444
478
 
@@ -1,54 +0,0 @@
1
- ---
2
- description: Bump version, tag, push, and create a GitHub release with auto-generated notes
3
- argument-hint: "[version e.g. 1.13.0 or rc4]"
4
- ---
5
-
6
- # Release
7
-
8
- Create a new release for this project.
9
-
10
- ## Inputs
11
-
12
- - `$ARGUMENTS` — target version or shorthand. Examples:
13
- - `1.13.0` — full version
14
- - `rc4` — shorthand for next RC (e.g. if current is `1.19.0-rc.3`, becomes `1.19.0-rc.4`)
15
- - If not provided, ask the user.
16
-
17
- ## Steps
18
-
19
- 1. **Determine version**: Use `$ARGUMENTS` or ask user. Handle `rc<N>` shorthand by reading current version from `package.json` and replacing/appending the RC suffix. Validate it's different from the current version.
20
-
21
- 2. **Detect prerelease**: If version contains `-rc.`, `-alpha.`, `-beta.`, treat as prerelease.
22
-
23
- 3. **Check working tree**: Run `git status`. If there are uncommitted changes, warn the user and stop.
24
-
25
- 4. **Bump version**:
26
- ```
27
- npm version <version> --no-git-tag-version
28
- ```
29
-
30
- 5. **Commit & push** (push to current branch, not hardcoded `main`):
31
- ```
32
- git add package.json package-lock.json
33
- git commit -m "🔖 chore: Bump version to <version>"
34
- git push origin HEAD
35
- ```
36
-
37
- 6. **Tag & push tag**:
38
- ```
39
- git tag v<version>
40
- git push origin v<version>
41
- ```
42
-
43
- 7. **Generate release notes**: Collect commits since the previous tag using `git log --oneline <prev-tag>..HEAD`. Write a **user-facing summary** grouped by:
44
- - Features (✨) — describe what was added, not raw commit messages
45
- - Fixes (🐛)
46
- - Other notable changes
47
- Include a "Full Changelog" compare link at the bottom.
48
-
49
- 8. **Create GitHub release** (add `--prerelease` flag for RC/alpha/beta):
50
- ```
51
- gh release create v<version> --title "v<version>" --notes "<notes>" [--prerelease]
52
- ```
53
-
54
- 9. **Report**: Show the release URL to the user.
@@ -1,36 +0,0 @@
1
- name: Deploy static site to Pages
2
-
3
- on:
4
- push:
5
- branches: ["main", "master"]
6
- paths:
7
- - "docs/**"
8
- workflow_dispatch:
9
-
10
- permissions:
11
- contents: read
12
- pages: write
13
- id-token: write
14
-
15
- concurrency:
16
- group: "pages"
17
- cancel-in-progress: false
18
-
19
- jobs:
20
- deploy:
21
- environment:
22
- name: github-pages
23
- url: ${{ steps.deployment.outputs.page_url }}
24
- runs-on: ubuntu-latest
25
- steps:
26
- - name: Checkout
27
- uses: actions/checkout@v4
28
- - name: Setup Pages
29
- uses: actions/configure-pages@v5
30
- - name: Upload artifact
31
- uses: actions/upload-pages-artifact@v3
32
- with:
33
- path: "docs"
34
- - name: Deploy to GitHub Pages
35
- id: deployment
36
- uses: actions/deploy-pages@v4
package/CLAUDE.md DELETED
@@ -1,49 +0,0 @@
1
- # CLAUDE.md
2
-
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
-
5
- ## What This Is
6
-
7
- Dashboard for visualizing all memory sources that influence Claude Code behavior. Shows the full memory stack: CLAUDE.md files (user/project/local), rules (`.claude/rules/*.md` with optional path-scoped frontmatter), auto memory (`~/.claude/projects/<encoded>/memory/`), and `@import` chains.
8
-
9
- ## Commands
10
-
11
- - `npm start` — run server (port 3459)
12
- - `npm run dev` — run with auto-open browser
13
- - `npx @biomejs/biome check public/app.js public/style.css` — lint
14
- - `npx @biomejs/biome format --write public/app.js public/style.css` — format
15
-
16
- ## Architecture
17
-
18
- Single-file Express backend + vanilla JS frontend. No build step, no framework.
19
-
20
- - **`server.js`** — Express server with two main responsibilities:
21
- 1. **Filesystem scanning** (`discoverMemorySources`) — walks `~/.claude/`, ancestor directories, project `.claude/rules/`, and auto memory dirs to build the full memory source stack
22
- 2. **API endpoints** — `/api/stack`, `/api/summary`, `/api/file`, `/api/rules/match`, `/api/imports` etc.
23
- - **`public/app.js`** — SPA with tree panel (left) + preview panel (right) split layout. Fetches from API, renders tree grouped by scope, shows syntax-highlighted preview with frontmatter badges and clickable `@import` links.
24
- - **`public/style.css`** — CSS variables on `:root` (dark default), `body.light` overrides. Scope colors: user=blue, project=green, local=yellow, rule=purple, memory=orange, policy=red.
25
-
26
- Both JS files use `// #region` / `// #endregion` markers for code organization.
27
-
28
- ## Key Server Concepts
29
-
30
- - **Project path encoding**: Claude Code stores auto memory in `~/.claude/projects/<encoded-path>/memory/`. The encoding replaces `/` with `-` and strips `:` from drive letters. `findMemoryDir()` tries exact match first, falls back to substring matching.
31
- - **Import resolution**: `@path/to/file.md` references are parsed from content. `resolveExistingImports()` resolves paths and filters out non-existent files. Imported files are recursively added to the stack (max 5 levels).
32
- - **Frontmatter parsing**: YAML frontmatter in rules files (`paths`, `type`, `name`, `description`) determines conditional loading. `parseFrontmatter()` handles both inline values and YAML array syntax.
33
- - **Rules matching**: `micromatch` glob matching against rule `paths` frontmatter via `/api/rules/match?file=`.
34
- - **Cache**: 30-second TTL on `discoverMemorySources` results, cleared on project switch or manual refresh.
35
-
36
- ## Conventions
37
-
38
- - Dark theme default, light theme via `body.light` class
39
- - Accent color: `#e86f33`
40
- - Fonts: IBM Plex Mono (data/code), Playfair Display (headings)
41
- - Keyboard-driven: j/k navigation, t=theme, r=refresh, e=open in editor, ?=help
42
- - Port 3459 (cost=3458, marketplace=3460)
43
- - `zoom: 1.25` on body for proportional scaling
44
- - No token/context budget estimation — only line/byte counts (deliberate decision)
45
-
46
- ## Prior Art
47
-
48
- - `../claude-code-cost` — same stack, data visualization patterns
49
- - `../claude-code-marketplace` — file tree, markdown preview, project picker, Highlight.js usage
Binary file
Binary file
package/biome.json DELETED
@@ -1,33 +0,0 @@
1
- {
2
- "$schema": "https://biomejs.dev/schemas/2.4.10/schema.json",
3
- "formatter": {
4
- "enabled": true,
5
- "indentStyle": "space",
6
- "indentWidth": 2,
7
- "lineWidth": 120
8
- },
9
- "linter": {
10
- "enabled": true,
11
- "rules": {
12
- "style": {
13
- "noDescendingSpecificity": "off"
14
- },
15
- "suspicious": {
16
- "useIterableCallbackReturn": "off"
17
- }
18
- }
19
- },
20
- "javascript": {
21
- "formatter": {
22
- "quoteStyle": "single"
23
- }
24
- },
25
- "css": {
26
- "formatter": {
27
- "quoteStyle": "single"
28
- }
29
- },
30
- "files": {
31
- "includes": ["public/app.js", "public/style.css"]
32
- }
33
- }
Binary file
Binary file