@sksoftofficial/mindroot 1.2.0 → 1.3.0

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.
@@ -4,34 +4,28 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <meta name="theme-color" content="#161616">
7
- <link rel="icon" type="image/png" sizes="64x64" href="/favicon.png">
7
+ <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40'%3E%3Cpath d='M20 18v17m0-10-8 6m8-6 8 6' fill='none' stroke='%23e9973f' stroke-width='2.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M20 21C10 21 5 15 6 7c8-1 14 4 14 14Z' fill='%23e9973f'/%3E%3Cpath d='M22 17C22 9 27 4 35 5c0 8-5 13-13 12Z' fill='%23f2bd7e'/%3E%3C/svg%3E">
8
8
  <title>Mindroot</title>
9
9
  <style>
10
10
  :root { --bg:#1e1e1e; --bg2:#161616; --bg3:#2b2b2b; --hover:#303030; --fg:#dcddde; --dim:#999; --accent:#e9973f; --accent-strong:#d97721; --accent-soft:#442f20; --danger:#ff6b6b; --ok:#53c59f; --border:rgba(255,255,255,.055); --border-soft:rgba(255,255,255,.03); --border-hover:rgba(255,255,255,.11); --border-accent:rgba(233,151,63,.22); }
11
- * { box-sizing:border-box; margin:0; padding:0; }
11
+ * { box-sizing:border-box; margin:0; padding:0; outline:none !important; }
12
12
  * { scrollbar-width:thin; scrollbar-color:#505050 transparent }
13
13
  *::-webkit-scrollbar { width:10px; height:10px }
14
14
  *::-webkit-scrollbar-track { background:transparent }
15
15
  *::-webkit-scrollbar-thumb { background:#484848; border:2px solid transparent; border-radius:999px; background-clip:padding-box }
16
16
  *::-webkit-scrollbar-thumb:hover { background:#606060; border:2px solid transparent; background-clip:padding-box }
17
17
  *::-webkit-scrollbar-corner { background:transparent }
18
- body { font:14px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background:var(--bg); color:var(--fg); height:100vh; height:100dvh; display:flex; flex-direction:column; }
19
- header { position:relative; z-index:2; height:74px; flex:none; display:flex; align-items:center; gap:18px; padding:18px 14px; background:var(--bg2); border-bottom:1px solid var(--border-soft); box-shadow:0 8px 20px rgba(0,0,0,.14); }
18
+ body { font:14px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background:var(--bg); color:var(--fg); min-height:100vh; min-height:100dvh; display:flex; flex-direction:column; }
19
+ header { position:relative; z-index:2; flex:none; display:flex; align-items:center; gap:18px; padding:12px 24px; background:var(--bg2); border-bottom:1px solid var(--border-soft); box-shadow:0 8px 20px rgba(0,0,0,.14); }
20
20
  .brand { display:flex; align-items:center; gap:10px; min-width:0 }
21
21
  .brand-mark { width:36px; height:36px; flex:none; display:grid; place-items:center; background:#211c19; border:1px solid rgba(233,151,63,.14); border-radius:8px; box-shadow:0 1px 2px rgba(0,0,0,.3), inset 0 1px rgba(255,255,255,.025) }
22
22
  .brand-logo { width:26px; height:26px; object-fit:contain; flex:none }
23
23
  .brand-copy { min-width:0; line-height:1.15 }
24
24
  header h1 { font-size:15px; font-weight:680; letter-spacing:-.015em; color:var(--fg); }
25
25
  .brand-subtitle { display:block; margin-top:3px; color:#aaa; font-size:11px; white-space:nowrap }
26
- .header-context { min-width:0; display:flex; align-items:center; gap:8px; color:var(--dim); font-size:12px }
27
- .header-context[hidden] { display:none }
28
- .header-divider { width:1px; height:24px; flex:none; background:var(--border-soft) }
29
- .header-context svg { width:15px; height:15px; flex:none; stroke:currentColor }
30
- #headerProject { max-width:280px; overflow:hidden; color:#b9b9b9; text-overflow:ellipsis; white-space:nowrap }
31
26
  header .spacer { flex:1 }
32
- .header-lock { height:34px; padding:0 10px; display:flex; align-items:center; gap:7px; color:#aaa; background:transparent; transition:background-color 120ms ease-out, border-color 120ms ease-out, color 120ms ease-out }
33
- .header-lock:hover { color:var(--fg); background:var(--hover) }
34
- .header-lock:focus-visible { outline:2px solid var(--accent); outline-offset:2px }
27
+ .header-lock { padding:5px 10px; display:flex; align-items:center; gap:7px; font-size:12px; color:var(--dim); background:transparent; border-color:transparent; transition:background-color 120ms ease-out, border-color 120ms ease-out, color 120ms ease-out }
28
+ .header-lock:hover { color:var(--fg); background:#ffffff0c; border-color:#ffffff40 }
35
29
  .header-lock svg { width:16px; height:16px; stroke:currentColor }
36
30
  .header-lock-label { font-size:12px }
37
31
  button { background:var(--bg3); color:var(--fg); border:1px solid var(--border); border-radius:5px; padding:5px 10px; cursor:pointer; font:inherit; }
@@ -40,35 +34,23 @@
40
34
  button.primary:hover { background:#e58732; border-color:#e58732 }
41
35
  button.danger { color:var(--danger) }
42
36
  input, textarea { background:#242424; color:var(--fg); border:1px solid var(--border); border-radius:5px; padding:6px 9px; font:inherit }
43
- input:focus, textarea:focus { outline:none; border-color:rgba(233,151,63,.68); box-shadow:0 0 0 2px rgba(233,151,63,.12) }
37
+ input:focus, textarea:focus { outline:none; }
44
38
  #gate { margin:auto; text-align:center; display:flex; flex-direction:column; gap:14px; width:min(420px, calc(100% - 32px)); padding:28px; background:var(--bg2); border:1px solid var(--border); border-radius:10px; box-shadow:0 12px 40px rgba(0,0,0,.28) }
45
39
  #gate input { width:100% }
46
- main { display:flex; flex:1; overflow:hidden }
47
- aside { position:relative; z-index:1; width:230px; background:var(--bg2); border-right:1px solid var(--border-soft); overflow-y:auto; padding:10px 14px; box-shadow:8px 0 20px rgba(0,0,0,.12); display:flex; flex-direction:column; gap:8px; transition:width 160ms ease-out }
48
- .sidebar-toggle { flex:none; height:34px; padding:0; display:flex; align-items:center; justify-content:center }
49
- .sidebar-toggle svg { width:17px; height:17px; stroke:currentColor; transition:transform 160ms ease-out }
40
+ main { display:flex; flex:1 }
41
+ aside { position:relative; z-index:1; width:230px; background:var(--bg2); border-right:1px solid var(--border-soft); padding:10px 14px; box-shadow:8px 0 20px rgba(0,0,0,.12); display:flex; flex-direction:column; gap:8px; transition:width 160ms ease-out }
50
42
  .sidebar-item { width:100%; display:flex; align-items:center; gap:9px; padding:8px 10px; text-align:left; color:var(--dim); background:transparent; border-color:transparent; white-space:nowrap }
51
43
  .sidebar-item svg { width:17px; height:17px; flex:none; stroke:currentColor }
52
44
  .sidebar-item.active { background:var(--accent-soft); color:#f2b46f }
53
- .sidebar-item:focus-visible, .proj:focus-visible { outline:2px solid var(--accent); outline-offset:-2px }
54
45
  .sidebar-item .menu-chevron { margin-left:auto; width:13px; transition:transform 160ms ease-out }
55
46
  .sidebar-item[aria-expanded="true"] .menu-chevron { transform:rotate(90deg) }
56
47
  #projectMenu { min-width:0 }
57
48
  #projList[hidden] { display:none }
58
- @media (min-width:701px) {
59
- aside.collapsed { width:56px; padding:10px 12px }
60
- aside.collapsed .sidebar-toggle svg { transform:rotate(180deg) }
61
- aside.collapsed .sidebar-item { padding:8px 0; justify-content:center }
62
- aside.collapsed .sidebar-label, aside.collapsed .menu-chevron { display:none }
63
- aside.collapsed #projList { margin-left:0; padding-left:0; border-left:0 }
64
- aside.collapsed .proj { padding:7px 0; text-align:center; font-size:0 }
65
- aside.collapsed .proj::before { content:attr(data-initial); font-size:12px; line-height:inherit }
66
- }
67
49
  #projList { display:flex; flex-direction:column; gap:2px; margin:4px 0 0 18px; padding-left:8px; border-left:1px solid var(--border) }
68
50
  .proj { padding:7px 10px; border-radius:5px; cursor:pointer; color:var(--dim); background:transparent; border:0; text-align:left; overflow-wrap:anywhere }
69
51
  .proj:hover { background:var(--hover); color:var(--fg) }
70
52
  .proj.active { background:var(--accent-soft); color:#f2b46f }
71
- #content { flex:1; min-width:0; overflow:hidden; padding:18px 22px; display:flex; flex-direction:column }
53
+ #content { flex:1; min-width:0; padding:18px 22px; display:flex; flex-direction:column }
72
54
  .workspace-head { flex:none; display:flex; align-items:center; gap:12px; min-height:36px; margin-bottom:16px; padding-bottom:12px; border-bottom:1px solid var(--border) }
73
55
  .tabs { display:flex; gap:4px; align-items:center }
74
56
  .tab { padding:5px 12px; border-radius:5px; cursor:pointer; color:var(--dim); border:1px solid transparent }
@@ -77,9 +59,9 @@
77
59
  .tab.active { color:#f2b46f; border-color:var(--border-accent); background:var(--accent-soft) }
78
60
  .project-actions { margin-left:auto; display:flex; align-items:center; gap:10px }
79
61
  .delete-project { white-space:nowrap }
80
- .view { flex:1; min-height:0; overflow-y:auto; width:100% }
81
- .graph-view { display:flex; flex-direction:column; gap:12px; overflow:hidden }
82
- .graph-workspace { display:flex; flex:1; min-height:0; border:1px solid var(--border); border-radius:8px; overflow:hidden; background:var(--bg2) }
62
+ .view { flex:1; min-height:0; width:100% }
63
+ .graph-view { display:flex; flex-direction:column; gap:12px }
64
+ .graph-workspace { display:flex; flex:1; min-height:500px; border:1px solid var(--border); border-radius:8px; overflow:hidden; background:var(--bg2) }
83
65
  .graph-stage { position:relative; flex:1; min-width:0; overflow:hidden }
84
66
  .graph-canvas { position:absolute; inset:0; touch-action:none }
85
67
  .graph-legend { position:absolute; left:16px; top:14px; display:flex; gap:16px; color:var(--dim); font-size:12px; pointer-events:none }
@@ -89,9 +71,8 @@
89
71
  .graph-controls { position:absolute; bottom:14px; left:14px; display:flex; gap:5px }
90
72
  .graph-controls button { min-width:34px }
91
73
  .graph-hint { color:var(--dim); font-size:11px; flex:none }
92
- .graph-view button:focus-visible, .tab:focus-visible { outline:2px solid var(--accent); outline-offset:2px }
93
74
  .docrow { padding:11px 12px; border:1px solid var(--border); border-radius:7px; margin-bottom:8px; cursor:pointer; background:#242424 }
94
- .docrow:hover { background:#282828; border-color:var(--border-hover) }
75
+ .docrow:hover { background:#282828; }
95
76
  .doc-head { display:flex; align-items:baseline; justify-content:space-between; gap:12px; margin-bottom:5px }
96
77
  .docrow .t { color:var(--fg); font-weight:600 }
97
78
  .docrow .p { color:var(--dim); font-size:12px }
@@ -109,7 +90,7 @@
109
90
  button:disabled { opacity:.5; cursor:default }
110
91
  .skill-fields [hidden], .editor-head .viewmode[hidden] { display:none }
111
92
  .sec-chip { display:inline-block; background:var(--bg3); border-radius:4px; padding:0 6px; margin:3px 4px 0 0; font-size:12px; color:#aaa }
112
- textarea.editor { width:100%; min-height:420px; resize:vertical; white-space:pre; line-height:1.5; font-family:ui-monospace, SFMono-Regular, Menlo, monospace }
93
+ textarea.editor { width:100%; min-height:420px; resize:vertical; white-space:pre-wrap; overflow-wrap:anywhere; line-height:1.5; font-family:ui-monospace, SFMono-Regular, Menlo, monospace }
113
94
  .rem { border:1px solid var(--border); border-radius:7px; padding:10px 12px; margin-bottom:8px; display:flex; gap:10px; align-items:flex-start; background:#242424 }
114
95
  .rem .txt { flex:1 }
115
96
  .rem a { color:var(--accent); text-decoration:none; font-size:12px }
@@ -118,7 +99,7 @@
118
99
  .err { color:var(--danger) }
119
100
  .empty { color:var(--dim); border:1px dashed var(--border); border-radius:7px; padding:28px 20px; text-align:center }
120
101
  h2 { font-size:13px; text-transform:uppercase; letter-spacing:.1em; color:var(--dim); margin:18px 0 10px }
121
- .editor-view { display:flex; flex-direction:column; overflow:hidden; padding-right:0 }
102
+ .editor-view { display:flex; flex-direction:column; padding-right:0 }
122
103
  .editor-head { flex:none }
123
104
  .editorbar { display:grid; grid-template-columns:minmax(0,1fr) auto minmax(0,1fr); align-items:center; gap:14px; min-height:42px; margin-bottom:10px }
124
105
  .editor-identity, .editor-actions { display:flex; align-items:center; gap:8px; min-width:0 }
@@ -127,15 +108,12 @@
127
108
  .editor-document { min-width:0 }
128
109
  .editor-title { max-width:320px; color:var(--fg); font-size:13px; font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
129
110
  .editor-actions { justify-self:end }
130
- .viewmode { display:inline-flex; gap:2px; padding:2px; background:#191919; border:1px solid var(--border); border-radius:6px }
131
- .viewmode button { padding:4px 10px; background:transparent; border-color:transparent }
132
- .viewmode button:hover { background:var(--hover); border-color:transparent }
133
- .viewmode button.on { background:var(--accent-soft); color:#f2b46f; border-color:transparent }
134
- .split { display:flex; flex:1; min-height:0; gap:12px; align-items:stretch }
135
- .split textarea.editor { flex:1; height:100%; min-height:0; resize:none; overflow-y:auto }
136
- .split.preview { display:block; overflow-y:auto }
111
+ .viewmode { display:inline-flex; gap:24px }
112
+ .split { display:flex; flex:none; min-height:0; gap:12px; align-items:stretch }
113
+ .split textarea.editor { flex:1; height:auto; min-height:420px; resize:none; overflow-y:hidden; field-sizing:content }
114
+ .split.preview { display:block }
137
115
  .split.preview .md { width:100%; min-height:100%; overflow:visible }
138
- .md { flex:1; overflow-y:auto; padding:24px 28px; border:1px solid var(--border); border-radius:7px; background:#242424; font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif }
116
+ .md { flex:1; padding:24px 28px; border:1px solid var(--border); border-radius:7px; background:#242424; font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif }
139
117
  .md h1, .md h2, .md h3, .md h4 { margin:18px 0 10px; line-height:1.3; color:var(--fg) }
140
118
  .md h1 { font-size:26px } .md h2 { font-size:20px } .md h3 { font-size:16px } .md h4 { font-size:14px }
141
119
  .md h2 { text-transform:none; letter-spacing:normal }
@@ -166,15 +144,11 @@
166
144
  .header-lock { min-width:44px; height:44px; justify-content:center }
167
145
  }
168
146
  @media (max-width:700px) {
169
- header { gap:10px; padding:14px 10px }
147
+ header { gap:10px }
170
148
  .brand { gap:8px }
171
149
  .brand-mark { width:34px; height:34px }
172
150
  .brand-logo { width:24px; height:24px }
173
151
  .brand-subtitle { display:none }
174
- .header-context { flex:1 }
175
- .header-divider { height:20px }
176
- #headerProject { max-width:none }
177
- header .spacer { display:none }
178
152
  .header-lock { justify-content:center; padding:0 }
179
153
  .header-lock-label { display:none }
180
154
  main { flex-direction:column }
@@ -185,13 +159,10 @@
185
159
  .workspace-head { align-items:flex-start; flex-wrap:wrap; gap:8px; margin-bottom:12px; padding-bottom:10px }
186
160
  button, input, select { min-height:44px; touch-action:manipulation }
187
161
  .header-lock, .back-button { width:44px; height:44px }
188
- .sidebar-toggle { display:none }
189
162
  .proj, .tab { min-height:44px; display:flex; align-items:center }
190
163
  .tab { padding:5px 8px }
191
164
  .delete-project { padding:5px 9px }
192
- .graph-view { overflow-y:auto }
193
165
  .graph-workspace { min-height:300px }
194
- .editor-view { overflow-y:auto }
195
166
  .editorbar { grid-template-columns:minmax(0,1fr) auto; gap:8px }
196
167
  .editor-title { max-width:180px }
197
168
  .viewmode { grid-column:1 / -1; grid-row:2; justify-self:center }
@@ -200,6 +171,202 @@
200
171
  .md { padding:20px }
201
172
  .split.preview .md { min-height:calc(100vh - 240px) }
202
173
  }
174
+ /* Workspace polish: retain the charcoal and amber identity. */
175
+ :root { color-scheme:dark; --border:rgba(255,255,255,.08); --dim:#a3a3a3 }
176
+ button, input, textarea, select { border-radius:8px }
177
+ button { transition:background-color 140ms, border-color 140ms }
178
+ header { box-shadow:none; border-bottom:1px solid var(--border) }
179
+ .brand { min-width:214px; gap:11px }
180
+ .brand-mark { width:38px; height:38px; background:none; border:0; box-shadow:none }
181
+ .brand-logo { width:38px; height:38px }
182
+ header h1 { font-size:20px; letter-spacing:-.04em }
183
+ .brand-subtitle { font-size:10px; margin-top:4px; color:var(--dim) }
184
+ aside { width:252px; padding:18px 14px; box-shadow:none; border-right:1px solid var(--border); gap:0 }
185
+ #projectMenu { display:flex; flex-direction:column; min-height:0 }
186
+ .sidebar-item { position:relative; font-size:11px; text-transform:uppercase; letter-spacing:.09em; font-weight:600; padding:9px 12px; min-height:40px; flex:none; border:0; border-radius:6px }
187
+ .sidebar-item:hover { background:var(--hover); color:var(--fg) }
188
+ .sidebar-item.active { background:#ffffff08; color:var(--fg) }
189
+ .sidebar-item.active::after { content:""; position:absolute; left:0; top:0; bottom:0; width:2px; border-radius:2px; background:var(--accent) }
190
+ .project-search { width:100%; margin:10px 0 14px; padding:9px 11px; font-size:12px; background:#ffffff04; border-color:transparent }
191
+ .project-search:focus { border-color:transparent; background:#ffffff08; box-shadow:none }
192
+ #projList { margin:0; padding:0 4px 0 0; border:0; gap:3px; min-height:0 }
193
+ .project-search { flex:none }
194
+ .project-empty { padding:12px 8px; color:var(--dim); font-size:12px }
195
+ .proj { position:relative; display:flex; align-items:center; gap:10px; padding:9px 12px; min-height:40px; flex:none; font-size:13px; border:0; border-radius:6px }
196
+ .proj::before { content:attr(data-initial); display:grid; place-items:center; width:24px; height:24px; flex:none; color:#777; font:10px ui-monospace,monospace }
197
+ .project-name { overflow:hidden; white-space:nowrap; text-overflow:ellipsis }
198
+ .proj.active { background:#ffffff08; color:var(--fg) }
199
+ .proj.active::before { color:var(--fg) }
200
+ .proj.active::after { content:""; position:absolute; left:0; top:0; bottom:0; width:2px; border-radius:2px; background:var(--accent) }
201
+ .mobile-project-picker { display:none }
202
+ #content { padding:30px 38px 24px }
203
+ .workspace-intro { flex:none; margin-bottom:24px }
204
+ .workspace-intro .eyebrow { color:var(--dim); font-size:10px; text-transform:uppercase; letter-spacing:.14em }
205
+ .workspace-intro h2 { margin:5px 0 4px; font-size:28px; line-height:1.3; font-weight:600; color:var(--fg); text-transform:none; letter-spacing:-.035em; overflow-wrap:anywhere }
206
+ .workspace-intro p { font-size:12px; color:var(--dim) }
207
+ .workspace-head { padding-bottom:0; margin-bottom:24px; min-height:44px }
208
+ .tabs { gap:24px; align-self:stretch }
209
+ .tab, .viewmode button { padding:10px 1px 13px; border:0; border-bottom:2px solid transparent; border-radius:0; font-size:13px; color:var(--dim); background:transparent }
210
+ button.tab.active, .viewmode button.on { background:transparent; border-color:var(--accent); color:var(--accent) }
211
+ button.tab:hover, .viewmode button:hover { background:transparent; color:var(--fg) }
212
+ button.danger { background:transparent; font-size:12px }
213
+ button.danger:hover { background:#ff6b6b0c; border-color:#ff6b6b40 }
214
+ .delete-project { border-color:transparent }
215
+ .delete-project svg { display:none; width:16px; height:16px }
216
+ .project-actions { padding-bottom:8px }
217
+ .collection-toolbar { display:flex; align-items:center; justify-content:space-between; gap:16px; margin-bottom:20px; background:var(--bg); padding-bottom:8px }
218
+ .collection-toolbar h3 { font-size:14px; font-weight:600; display:flex; align-items:center; gap:9px }
219
+ .collection-toolbar input { width:230px; font-size:12px; padding:8px 11px; background:var(--bg2) }
220
+ .collection-count { font-size:11px; color:var(--dim); background:var(--bg3); padding:1px 7px; border-radius:5px; font-variant-numeric:tabular-nums }
221
+ .collection-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); grid-auto-rows:max-content; gap:14px; align-content:start }
222
+ .collection-grid .collection-toolbar, .collection-grid .empty { grid-column:1 / -1 }
223
+ .collection-grid .collection-toolbar { margin-bottom:0 }
224
+ .docrow { display:flex; flex-direction:column; align-items:stretch; justify-content:flex-start; width:100%; text-align:left; padding:22px; margin-bottom:0; border-radius:10px; background:#242424; position:relative }
225
+ .docrow:hover { background:#282623 }
226
+ .doc-head { align-items:center; margin-bottom:10px }
227
+ .docrow .t { font-size:15px; letter-spacing:-.015em; overflow-wrap:anywhere }
228
+ .docrow .p { flex:none; font-size:11px }
229
+ .note-path { display:block; margin-bottom:18px; color:var(--dim); font:11px ui-monospace,monospace; overflow-wrap:anywhere }
230
+ .docrow .note-path + div { height:90px; overflow:hidden; flex:none }
231
+ .sec-chip { background:#ffffff03; border:1px solid var(--border); font-size:11px; padding:2px 7px; margin:3px 4px 0 0; max-width:100%; overflow-wrap:anywhere }
232
+ .skill-summary { line-height:1.8; font-size:13px }
233
+ .rem { padding:20px 22px; border-radius:10px; gap:24px; margin-bottom:10px }
234
+ .rem .txt { min-width:0; overflow-wrap:anywhere }
235
+ .txt-main { line-height:1.8; margin-bottom:8px }
236
+ .rem .meta { margin-top:8px; font-size:10px }
237
+ .rem > button { border-color:transparent; color:var(--dim); width:32px; height:32px; padding:7px; flex:none; display:grid; place-items:center }
238
+ .rem > button:hover, .rem > button:focus-visible { color:var(--danger) }
239
+ .rem > button svg { width:16px; height:16px }
240
+ .empty { padding:40px 24px; border-radius:10px }
241
+ .editorbar { margin-bottom:24px; border-bottom:1px solid var(--border); padding-bottom:0; align-items:stretch }
242
+ .editor-identity, .editor-actions { padding-bottom:8px }
243
+ .viewmode { align-self:stretch }
244
+ .editor-actions button { min-height:34px; padding:5px 10px; font-size:12px; line-height:22px; font-weight:400; background:transparent; border-color:transparent }
245
+ .editor-actions .primary { color:var(--accent) }
246
+ .editor-actions .primary:hover { background:var(--hover); border-color:transparent }
247
+ .editor-actions .danger:hover { border-color:transparent }
248
+ .back-button { background:transparent; border-color:transparent; color:var(--dim) }
249
+ .back-button:hover { background:var(--hover); border-color:transparent; color:var(--fg) }
250
+ .editor-title { display:block; max-width:100% }
251
+ .md { padding:32px; line-height:1.85; border-radius:10px }
252
+ .split > .md, .split > textarea { min-width:0 }
253
+ .md p, .md li { overflow-wrap:anywhere }
254
+ .split.preview .md { padding:32px }
255
+ .md h1 { letter-spacing:-.035em; font-size:30px }
256
+ .md h2 { margin-top:30px }
257
+ .md blockquote { padding:8px 16px; background:#e9973f05 }
258
+ textarea.editor { padding:22px; line-height:1.8 }
259
+ textarea.editor:focus { border-color:var(--border); box-shadow:none; outline:none }
260
+ .graph-workspace { border-radius:10px }
261
+ .proj[hidden], .docrow[hidden], .rem[hidden], .empty[hidden] { display:none }
262
+ @media (min-width:1600px) { #content { padding-left:max(38px,calc((100vw - 1600px)/2)); padding-right:max(38px,calc((100vw - 1600px)/2)) } }
263
+ @media (max-width:1000px) { #content { padding:24px } aside { width:220px } .brand { min-width:182px } .collection-grid { grid-template-columns:1fr } }
264
+ @media (max-width:700px) {
265
+ header { gap:12px }
266
+ .brand { min-width:0 } header h1 { font-size:18px }
267
+ .brand-mark, .brand-logo { width:32px; height:32px }
268
+ aside { width:100%; padding:4px 16px; max-height:none; gap:0 }
269
+ #projectMenu { display:none }
270
+ .mobile-project-picker { display:flex; align-items:center; gap:16px; min-width:0 }
271
+ .mobile-project-picker span { font-size:10px; text-transform:uppercase; letter-spacing:.1em; color:var(--dim); flex:none }
272
+ .mobile-project-picker select { flex:1; min-width:0; width:0; background:var(--bg2); color:var(--fg); border:0; padding:8px; font:inherit; font-size:13px; min-height:44px }
273
+ .project-search { margin:4px 0 8px; padding:6px 10px }
274
+ #projList { margin:0; gap:5px; overflow-y:hidden; padding:0 }
275
+ #content { padding:20px 16px 16px }
276
+ .workspace-intro { margin-bottom:16px }
277
+ .workspace-intro h2 { font-size:24px }
278
+ .workspace-intro p { display:none }
279
+ .workspace-head { gap:6px; margin-bottom:18px }
280
+ .tabs, .viewmode { gap:18px } .tab, .viewmode button { padding:10px 0 }
281
+ .project-actions { padding-bottom:0 }
282
+ .delete-project { width:44px; height:44px; display:grid; place-items:center }
283
+ .delete-project svg { display:block }
284
+ .delete-project span { display:none }
285
+ .collection-toolbar { gap:12px }
286
+ .collection-toolbar h3 { flex:none }
287
+ .collection-toolbar input { width:0; flex:1; min-width:0 }
288
+ .docrow, .rem { padding:18px }
289
+ .rem { gap:10px }
290
+ .rem > button { width:44px; height:44px }
291
+ .doc-head { flex-wrap:wrap; gap:6px }
292
+ .split.preview .md, .md { padding:24px 20px }
293
+ .editor-title { max-width:140px }
294
+ .editor-actions { gap:5px }
295
+ .editor-actions button { min-height:44px; padding:7px 10px }
296
+ }
297
+ .home-link { margin-bottom:12px }
298
+ body:has(#app.home-active), body:has(#app.graph-active) { height:100dvh; min-height:0; overflow:hidden }
299
+ #app.home-active, #app.graph-active { min-height:0 }
300
+ #app.home-active > aside, #app.graph-active > aside { overflow-y:auto; overflow-x:hidden; flex-shrink:0 }
301
+ #app.graph-active #content { overflow:auto; padding-bottom:0 }
302
+ .project-graph-view { gap:0; min-height:350px }
303
+ .project-graph-view .graph-workspace { min-height:260px; border:0; border-radius:0 }
304
+ .project-graph-view .brain-footer { padding:18px 0 }
305
+ .project-graph-view .graph-legend { top:auto; bottom:24px; left:0; font-size:10px }
306
+ :root { --bg:#17191b; --bg2:#151719; --bg3:#252a2d; --hover:#2b3236 }
307
+ #content { background:radial-gradient(ellipse at 55% 47%,#162329 0%,#151a1d 37%,#17191b 70%); }
308
+ #content.home-content { padding:0; overflow:auto }
309
+ .collection-toolbar { background:transparent }
310
+ .docrow, .rem, .md { background:#ffffff03 }
311
+ .docrow:hover { background:#ffffff06 }
312
+ .graph-workspace { background:transparent; border-color:#ffffff0a }
313
+ .graph-legend { color:#889498 }
314
+ .graph-legend i { background:#efffff }
315
+ .graph-legend .memory-dot { background:#acf0ff }
316
+ .graph-controls button { background:#ffffff04; color:#b9c2c5; border-color:#ffffff0b }
317
+ .graph-controls button:hover { background:#ffffff09 }
318
+ .home-loading { display:grid; place-items:center; color:var(--dim); font-size:12px; letter-spacing:.02em }
319
+ .brain-view { gap:0; min-height:480px }
320
+ .brain-heading { display:flex; align-items:center; justify-content:space-between; gap:32px; padding:32px 40px 8px; flex:none }
321
+ .brain-eyebrow { display:block; color:#8b9295; font-size:10px; letter-spacing:.16em; text-transform:uppercase; margin-bottom:8px }
322
+ .brain-title h2 { color:#e9e9e5; font-size:30px; font-weight:500; line-height:1.2; letter-spacing:-.045em; text-transform:none; margin:0 }
323
+ .brain-title p { color:#8b9295; font-size:12px; margin-top:10px }
324
+ .brain-stats { display:flex; gap:30px; flex:none }
325
+ .brain-stats > div { display:flex; flex-direction:column-reverse; gap:4px }
326
+ .brain-stats dt { color:#8b9295; font-size:11px }
327
+ .brain-stats dd { color:#d1d9dc; font-size:22px; font-weight:400; letter-spacing:-.035em; font-variant-numeric:tabular-nums }
328
+ .brain-view .graph-workspace { min-height:280px; border:0; border-radius:0; background:transparent }
329
+ .brain-view .graph-legend { top:auto; bottom:24px; left:40px; color:#889498; font-size:10px; gap:14px }
330
+ .brain-view .graph-legend i { width:5px; height:5px; background:#efffff }
331
+ .brain-view .graph-legend .memory-dot { width:4px; height:4px; background:#91dfff }
332
+ .brain-interaction-hint { position:absolute; bottom:22px; left:50%; transform:translateX(-50%); color:#778387; font-size:10px; white-space:nowrap; pointer-events:none }
333
+ .brain-footer { flex:none; display:flex; align-items:center; justify-content:space-between; gap:20px; padding:18px 40px; border-top:1px solid #ffffff07 }
334
+ .brain-provenance { display:flex; align-items:center; gap:8px; color:#889498; font-size:11px }
335
+ .brain-status-dot { width:5px; height:5px; border-radius:50%; background:#85ada3; flex:none }
336
+ .brain-actions { display:flex; align-items:center; gap:16px; min-width:0 }
337
+ .brain-project-picker { max-width:220px; min-width:0; background:#ffffff04; color:#b9c2c5; border:1px solid #ffffff0b; padding:8px 28px 8px 12px; font:inherit; font-size:11px; cursor:pointer }
338
+ .brain-project-picker option { background:#1c2124; color:#dcddde }
339
+ .graph-view .graph-controls { position:static; gap:0; border:1px solid #ffffff0b; border-radius:8px; overflow:hidden; flex:none }
340
+ .graph-view .graph-controls button { min-width:34px; min-height:34px; background:transparent; color:#b9c2c5; border:0; border-radius:0 }
341
+ .graph-view .graph-controls button + button { border-left:1px solid #ffffff0b }
342
+ .graph-view .graph-controls button:hover, .brain-project-picker:hover { background:#ffffff09 }
343
+ .brain-view > .empty { margin:40px; flex:1; display:grid; place-items:center; color:var(--dim); font-size:13px }
344
+ @media (max-width:1000px) {
345
+ .brain-heading { padding:28px 24px 8px; gap:20px }
346
+ .brain-stats { gap:18px }
347
+ .brain-footer { padding:16px 24px }
348
+ .brain-gesture { display:none }
349
+ .brain-view .graph-legend { left:24px }
350
+ .brain-interaction-hint { left:auto; right:24px; transform:none }
351
+ }
352
+ @media (max-width:700px) {
353
+ .brain-view { min-height:510px }
354
+ .brain-heading { flex-wrap:wrap; gap:22px; padding:24px 24px 4px }
355
+ .brain-title h2 { font-size:28px }
356
+ .brain-title p { display:none }
357
+ .brain-stats { width:100%; justify-content:space-between; padding-right:24px }
358
+ .brain-stats dd { font-size:20px }
359
+ .brain-stats dt { font-size:10px }
360
+ .brain-view .graph-workspace { min-height:260px }
361
+ .brain-view .graph-legend { bottom:12px }
362
+ .brain-interaction-hint { display:none }
363
+ .brain-footer { flex-wrap:wrap; gap:12px; padding:14px 24px 20px }
364
+ .brain-provenance { font-size:10px }
365
+ .brain-actions { width:100%; justify-content:space-between }
366
+ .brain-project-picker { flex:1; max-width:none; min-height:44px }
367
+ .graph-view .graph-controls button { min-width:44px; min-height:44px }
368
+ }
369
+ @media (prefers-reduced-motion:reduce) { *, *::before, *::after { transition:none !important } }
203
370
  </style>
204
371
  <script src="https://cdn.jsdelivr.net/npm/marked@12.0.2/marked.min.js"></script>
205
372
  <script src="https://cdn.jsdelivr.net/npm/dompurify@3.1.6/dist/purify.min.js"></script>
@@ -208,20 +375,13 @@
208
375
  <header>
209
376
  <div class="brand">
210
377
  <span class="brand-mark" aria-hidden="true">
211
- <img class="brand-logo" src="/logo.png" alt="" draggable="false">
378
+ <svg class="brand-logo" viewBox="0 0 40 40" fill="none"><path d="M20 18v17m0-10-8 6m8-6 8 6" stroke="#e9973f" stroke-width="2.8" stroke-linecap="round" stroke-linejoin="round"/><path d="M20 21C10 21 5 15 6 7c8-1 14 4 14 14Z" fill="#e9973f"/><path d="M22 17C22 9 27 4 35 5c0 8-5 13-13 12Z" fill="#f2bd7e"/></svg>
212
379
  </span>
213
380
  <div class="brand-copy">
214
381
  <h1>mindroot</h1>
215
- <span class="brand-subtitle">Persistent memory</span>
382
+ <span class="brand-subtitle">Your second brain</span>
216
383
  </div>
217
384
  </div>
218
- <div class="header-context" hidden>
219
- <span class="header-divider" aria-hidden="true"></span>
220
- <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
221
- <path d="M4 7.5h6l2 2h8v9H4z" stroke-width="1.7" stroke-linejoin="round"/>
222
- </svg>
223
- <span id="headerProject" aria-live="polite"></span>
224
- </div>
225
385
  <div class="spacer"></div>
226
386
  <button class="header-lock" type="button" onclick="logout()" title="Lock dashboard" aria-label="Lock dashboard">
227
387
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
@@ -242,18 +402,20 @@
242
402
 
243
403
  <main id="app" style="display:none">
244
404
  <aside>
245
- <button class="sidebar-toggle" type="button" title="Shrink menu" aria-label="Shrink menu" aria-expanded="true" onclick="toggleSidebar()">
246
- <svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
247
- <path d="m14 7-5 5 5 5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
248
- </svg>
249
- </button>
405
+ <label class="mobile-project-picker"><span>Project</span><select id="mobileProject" aria-label="Choose project" onchange="selectProject(this.value)"></select></label>
250
406
  <div id="projectMenu">
251
- <button class="sidebar-item" id="projectMenuToggle" type="button" title="Project Memories" aria-label="Project Memories" aria-expanded="true" aria-controls="projList" onclick="toggleProjectMenu()">
407
+ <button class="sidebar-item home-link" id="homeLink" type="button" title="The Atlas" aria-label="The Atlas" onclick="navTo('#/')">
408
+ <svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M12 10.8 V21 M12 15 l-4.8 3.6 M12 15 l4.8 3.6" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 12.6 C6 12.6 3 9 3.6 4.2 c4.8 -.6 8.4 2.4 8.4 8.4 Z" stroke-width="1.6" stroke-linejoin="round"/><path d="M13.2 10.2 C13.2 5.4 16.2 2.4 21 3 c0 4.8 -3 7.8 -7.8 7.2 Z" stroke-width="1.6" stroke-linejoin="round"/></svg>
409
+ <span class="sidebar-label">The Atlas</span>
410
+ </button>
411
+ <button class="sidebar-item" id="projectMenuToggle" type="button" title="Projects" aria-label="Projects" aria-expanded="true" aria-controls="projList" onclick="toggleProjectMenu()">
252
412
  <svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M3 7h7l2 2h9v11H3zM3 7V4h7l2 3" stroke-width="1.7" stroke-linejoin="round"/></svg>
253
- <span class="sidebar-label">Project Memories</span>
413
+ <span class="sidebar-label">Projects</span>
254
414
  <svg class="menu-chevron" viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="m9 6 6 6-6 6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
255
415
  </button>
416
+ <input class="project-search" type="search" placeholder="Find a project…" aria-label="Find a project" oninput="filterProjects()">
256
417
  <div id="projList"></div>
418
+ <p class="project-empty" role="status" hidden>No projects match.</p>
257
419
  </div>
258
420
  </aside>
259
421
  <section id="content"></section>
@@ -283,6 +445,8 @@ async function api(method, url, body) {
283
445
  function showGate(msg) {
284
446
  disposeGraph?.();
285
447
  disposeGraph = null;
448
+ $("#content").replaceChildren();
449
+ $("#app").classList.remove("home-active", "graph-active");
286
450
  $("#app").style.display = "none";
287
451
  $("#gate").style.display = "flex";
288
452
  if (msg) $("#gateErr").textContent = msg;
@@ -304,9 +468,8 @@ async function boot() {
304
468
  $("#app").style.display = "flex";
305
469
  const projects = await renderProjects();
306
470
  const r = routeFromHash();
307
- if (projects.some((p) => p.slug === r.slug)) applyRoute(r);
308
- else if (projects.length) navTo("#/" + encodeURIComponent(projects[0].slug) + "/notes");
309
- else applyRoute(r);
471
+ if (!r.slug || projects.some((p) => p.slug === r.slug)) applyRoute(r);
472
+ else navTo("#/");
310
473
  } catch (e) {
311
474
  if (e.message !== "unauthorized") showGate(String(e));
312
475
  }
@@ -336,8 +499,12 @@ function applyRoute({ slug = null, tab = "notes", doc = null } = {}) {
336
499
  disposeGraph = null;
337
500
  activeHash = location.hash || "#/";
338
501
  currentProject = slug;
502
+ $("#mobileProject").value = slug || "";
339
503
  currentTab = tab;
340
504
  currentDoc = doc;
505
+ $("#homeLink").classList.toggle("active", !slug);
506
+ if (!slug) $("#homeLink").setAttribute("aria-current", "page");
507
+ else $("#homeLink").removeAttribute("aria-current");
341
508
  document.querySelectorAll(".proj").forEach((n) => {
342
509
  n.classList.toggle("active", n.dataset.slug === slug);
343
510
  if (n.dataset.slug === slug) n.setAttribute("aria-current", "page");
@@ -345,15 +512,9 @@ function applyRoute({ slug = null, tab = "notes", doc = null } = {}) {
345
512
  });
346
513
  if (slug) setProjectMenu(true);
347
514
  if (!slug) {
348
- const c = $("#content");
349
- c.innerHTML = '<div class="view"><div class="empty">No projects yet. Agents create them automatically on first write.</div></div>';
515
+ renderHome();
350
516
  return;
351
517
  }
352
- $(".header-context").hidden = false;
353
- const projectLabel = $("#headerProject");
354
- projectLabel.textContent = slug;
355
- projectLabel.title = projectLabel.textContent;
356
- projectLabel.setAttribute("aria-label", "Current project: " + slug);
357
518
  renderTabs();
358
519
  }
359
520
 
@@ -361,6 +522,9 @@ window.addEventListener("hashchange", () => applyRoute(routeFromHash()));
361
522
 
362
523
  async function renderProjects() {
363
524
  const rows = await api("GET", "/api/projects");
525
+ const picker = $("#mobileProject");
526
+ picker.replaceChildren(new Option("Home · All projects", ""), ...rows.map((r) => new Option(r.slug, r.slug)));
527
+ picker.value = currentProject || "";
364
528
  const el = $("#projList");
365
529
  el.innerHTML = "";
366
530
  for (const r of rows) {
@@ -368,26 +532,87 @@ async function renderProjects() {
368
532
  div.type = "button";
369
533
  div.className = "proj" + (r.slug === currentProject ? " active" : "");
370
534
  div.dataset.slug = r.slug;
535
+ div.setAttribute("aria-label", r.slug);
371
536
  div.dataset.initial = r.slug.slice(0, 2).toUpperCase();
372
- div.textContent = r.slug;
373
- div.title = r.docs + " notes, " + r.memories + " memories, " + (r.skills || 0) + " skills";
537
+ const name = document.createElement("span");
538
+ name.className = "project-name";
539
+ name.textContent = r.slug;
540
+ div.appendChild(name);
541
+ div.title = r.slug + " · " + r.docs + " notes, " + r.memories + " memories, " + (r.skills || 0) + " skills";
374
542
  div.onclick = () => selectProject(r.slug);
375
543
  el.appendChild(div);
376
544
  }
377
545
  if (!rows.length) el.innerHTML = '<p class="muted">No projects yet</p>';
546
+ filterProjects();
378
547
  return rows;
379
548
  }
380
549
 
550
+ function filterProjects() {
551
+ const query = $(".project-search").value.trim().toLowerCase();
552
+ const rows = [...document.querySelectorAll(".proj")];
553
+ rows.forEach((row) => { row.hidden = !row.dataset.slug.toLowerCase().includes(query); });
554
+ $(".project-empty").hidden = !query || rows.some((row) => !row.hidden);
555
+ }
556
+
381
557
  function selectProject(slug) {
382
- navTo(routeUrl(slug, "notes"));
558
+ navTo(slug ? routeUrl(slug, "notes") : "#/");
559
+ }
560
+
561
+ async function renderHome() {
562
+ const c = $("#content");
563
+ c.classList.add("home-content");
564
+ $("#app").classList.remove("graph-active");
565
+ $("#app").classList.add("home-active");
566
+ c.replaceChildren();
567
+ const view = document.createElement("div");
568
+ view.className = "view home-loading";
569
+ view.innerHTML = '<div role="status">Gathering your memories…</div>';
570
+ c.append(view);
571
+ try {
572
+ const projects = await api("GET", "/api/projects");
573
+ if (!view.isConnected) return;
574
+ const entries = new Array(projects.length);
575
+ let next = 0;
576
+ // Bound concurrency so a large workspace does not flood the server.
577
+ await Promise.all(Array.from({ length: Math.min(4, projects.length) }, async () => {
578
+ while (next < projects.length && view.isConnected) {
579
+ const index = next++;
580
+ const { slug } = projects[index];
581
+ entries[index] = { slug, data: await api("GET", "/api/projects/" + encodeURIComponent(slug)) };
582
+ }
583
+ }));
584
+ if (!view.isConnected || $("#app").style.display === "none") return;
585
+ const { mountGraph } = await import("/graph.js");
586
+ if (!view.isConnected) return;
587
+ const dispose = await mountGraph(view, entries, { brain: true, openProject: (slug) => navTo(routeUrl(slug, "graph")) });
588
+ if (!view.isConnected) dispose?.();
589
+ else disposeGraph = dispose;
590
+ } catch (e) {
591
+ if (!view.isConnected) return;
592
+ view.innerHTML = '<div class="empty err"></div>';
593
+ view.firstChild.textContent = "Unable to load the brain: " + e.message;
594
+ const retry = document.createElement("button");
595
+ retry.textContent = "Try again";
596
+ retry.onclick = () => navTo("#/");
597
+ view.append(retry);
598
+ }
383
599
  }
384
600
 
385
601
  function renderTabs() {
602
+ window.scrollTo(0, 0);
386
603
  disposeGraph?.();
387
604
  disposeGraph = null;
388
605
  const c = $("#content");
606
+ c.classList.remove("home-content");
607
+ $("#app").classList.remove("home-active");
608
+ $("#app").classList.toggle("graph-active", currentTab === "graph");
389
609
  c.innerHTML = "";
390
610
  if (!(["notes", "skills"].includes(currentTab) && currentDoc !== null)) {
611
+ const intro = document.createElement("div");
612
+ intro.className = "workspace-intro";
613
+ intro.innerHTML = '<div class="eyebrow">Project workspace</div><h2></h2>';
614
+ intro.querySelector("h2").textContent = currentProject;
615
+ c.appendChild(intro);
391
616
  const head = document.createElement("div");
392
617
  head.className = "workspace-head";
393
618
  const tabs = document.createElement("div");
@@ -403,7 +628,7 @@ function renderTabs() {
403
628
  }
404
629
  const actions = document.createElement("div");
405
630
  actions.className = "project-actions";
406
- actions.innerHTML = '<button class="danger delete-project">Delete project</button>';
631
+ actions.innerHTML = '<button class="danger delete-project" aria-label="Delete project" title="Delete project"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13M10 11v5M14 11v5"/></svg><span>Delete project</span></button>';
407
632
  actions.querySelector("button").onclick = deleteCurrentProject;
408
633
  head.append(tabs, actions);
409
634
  c.appendChild(head);
@@ -450,39 +675,52 @@ async function deleteCurrentProject() {
450
675
 
451
676
  async function renderNotes(view) {
452
677
  const docs = await api("GET", "/api/projects/" + currentProject + "/docs");
678
+ if (!view.isConnected) return;
453
679
  if (currentDoc !== null) return renderEditor(view, docs);
680
+ view.classList.add("collection-grid");
454
681
  for (const d of docs) {
455
- const row = document.createElement("div");
682
+ const row = document.createElement("button");
683
+ row.type = "button";
456
684
  row.className = "docrow";
457
685
  row.innerHTML = '<div class="doc-head"><div class="t"></div><div class="p">' + d.sections.length + ' sections</div></div>';
458
686
  row.querySelector(".t").textContent = d.title + " ";
687
+ const path = document.createElement("span");
688
+ path.className = "note-path";
689
+ path.textContent = d.path;
690
+ row.appendChild(path);
459
691
  const chips = d.sections.filter(Boolean).map((s) => '<span class="sec-chip">' + esc(s) + "</span>").join("");
460
692
  row.insertAdjacentHTML("beforeend", '<div>' + chips + "</div>");
461
693
  row.onclick = () => navTo(routeUrl(currentProject, "notes", d.path));
462
694
  view.appendChild(row);
463
695
  }
464
696
  if (!docs.length) view.innerHTML = '<div class="empty">No notes yet. Agents create them with <b>mindroot_save_note</b>.</div>';
697
+ addCollectionToolbar(view, "Notes", docs.length);
465
698
  }
466
699
 
467
- let mdMode = localStorage.getItem("mindroot_mdmode") || "preview";
468
-
469
- let sidebarCollapsed = localStorage.getItem("mindroot_sidebar") === "collapsed";
470
- function toggleSidebar() { setSidebar(!sidebarCollapsed); }
471
- function setSidebar(collapsed) {
472
- sidebarCollapsed = collapsed;
473
- localStorage.setItem("mindroot_sidebar", collapsed ? "collapsed" : "expanded");
474
- applySidebar();
475
- }
476
- function applySidebar() {
477
- const aside = document.querySelector("main aside");
478
- const btn = $(".sidebar-toggle");
479
- aside.classList.toggle("collapsed", sidebarCollapsed);
480
- btn.setAttribute("aria-expanded", String(!sidebarCollapsed));
481
- const label = sidebarCollapsed ? "Expand menu" : "Shrink menu";
482
- btn.title = label;
483
- btn.setAttribute("aria-label", label);
700
+ function addCollectionToolbar(view, label, count) {
701
+ const toolbar = document.createElement("div");
702
+ toolbar.className = "collection-toolbar";
703
+ toolbar.innerHTML = '<h3>' + label + '<span class="collection-count">' + count + '</span></h3><input type="search" placeholder="Filter ' + label.toLowerCase() + '…" aria-label="Filter ' + label.toLowerCase() + '">';
704
+ const empty = document.createElement("div");
705
+ empty.className = "empty";
706
+ empty.hidden = true;
707
+ empty.setAttribute("role", "status");
708
+ empty.textContent = "No matches. Try a different search.";
709
+ toolbar.querySelector("input").oninput = (event) => {
710
+ const query = event.target.value.trim().toLowerCase();
711
+ let visible = 0;
712
+ view.querySelectorAll(".docrow, .rem").forEach((row) => {
713
+ row.hidden = !row.textContent.toLowerCase().includes(query);
714
+ if (!row.hidden) visible++;
715
+ });
716
+ toolbar.querySelector(".collection-count").textContent = query ? visible + " / " + count : count;
717
+ empty.hidden = visible > 0 || count === 0;
718
+ };
719
+ view.prepend(toolbar);
720
+ view.appendChild(empty);
484
721
  }
485
- applySidebar();
722
+
723
+ let mdMode = localStorage.getItem("mindroot_mdmode") || "preview";
486
724
 
487
725
  let projectMenuExpanded = localStorage.getItem("mindroot_project_menu") !== "collapsed";
488
726
  function toggleProjectMenu() { setProjectMenu(!projectMenuExpanded); }
@@ -491,6 +729,9 @@ function setProjectMenu(expanded) {
491
729
  localStorage.setItem("mindroot_project_menu", expanded ? "expanded" : "collapsed");
492
730
  $("#projectMenuToggle").setAttribute("aria-expanded", String(expanded));
493
731
  $("#projList").hidden = !expanded;
732
+ $(".project-search").hidden = !expanded;
733
+ if (!expanded) $(".project-empty").hidden = true;
734
+ else filterProjects();
494
735
  }
495
736
  setProjectMenu(projectMenuExpanded);
496
737
 
@@ -528,6 +769,7 @@ async function renderEditor(view, docs) {
528
769
  wrap.className = "split " + mdMode;
529
770
  const ta = document.createElement("textarea");
530
771
  ta.className = "editor";
772
+ ta.setAttribute("aria-label", "Note content");
531
773
  ta.value = doc.content;
532
774
  ta.spellcheck = false;
533
775
  let prev = null;
@@ -563,6 +805,7 @@ async function renderEditor(view, docs) {
563
805
 
564
806
  async function renderMemories(view) {
565
807
  const rows = await api("GET", "/api/projects/" + currentProject + "/memories");
808
+ if (!view.isConnected) return;
566
809
  for (const r of rows) {
567
810
  const div = document.createElement("div");
568
811
  div.className = "rem";
@@ -572,12 +815,16 @@ async function renderMemories(view) {
572
815
  '<div class="meta">#' + r.id + " · " + r.target_type + " · " + new Date(r.updated_at).toISOString().slice(0,16).replace("T"," ") + "</div></div>";
573
816
  div.querySelector(".txt-main").textContent = r.text;
574
817
  const del = document.createElement("button");
575
- del.className = "danger"; del.textContent = "Delete";
818
+ del.className = "danger";
819
+ del.title = "Delete memory";
820
+ del.setAttribute("aria-label", "Delete memory #" + r.id);
821
+ del.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13M10 11v5M14 11v5"/></svg>';
576
822
  del.onclick = async () => { await api("DELETE", "/api/projects/" + currentProject + "/memories/" + r.id); renderTabs(); };
577
823
  div.appendChild(del);
578
824
  view.appendChild(div);
579
825
  }
580
826
  if (!rows.length) view.insertAdjacentHTML("beforeend", '<div class="empty">No memories yet</div>');
827
+ addCollectionToolbar(view, "Memories", rows.length);
581
828
  view.querySelectorAll("a[data-p]").forEach((a) =>
582
829
  a.onclick = (e) => { e.preventDefault(); navTo(routeUrl(currentProject, "notes", a.dataset.p)); });
583
830
  }
@@ -587,6 +834,7 @@ async function renderSkills(view) {
587
834
  if (currentDoc !== null) return renderSkillEditor(view, slug, currentDoc);
588
835
  const rows = await api("GET", "/api/projects/" + encodeURIComponent(slug) + "/skills");
589
836
  if (!view.isConnected) return;
837
+ view.classList.add("collection-grid");
590
838
  for (const skill of rows) {
591
839
  const row = document.createElement("button");
592
840
  row.className = "docrow skillrow";
@@ -598,6 +846,7 @@ async function renderSkills(view) {
598
846
  view.appendChild(row);
599
847
  }
600
848
  if (!rows.length) view.innerHTML = '<div class="empty">No skills yet. Saved procedures from your agents will appear here.</div>';
849
+ addCollectionToolbar(view, "Skills", rows.length);
601
850
  }
602
851
 
603
852
  async function renderSkillEditor(view, slug, name) {