mop-agent 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,8 +5,9 @@ through MOP-FLOW. It stores project memory, performs semantic recall and
5
5
  consolidation, serves grounded chat, and can request approved actions from a
6
6
  linked FLOW node.
7
7
 
8
- > **Release status:** npm package `mop-agent@0.1.10` contains the corrected VPS
9
- > installer, one-time Admin setup/login flow, and shared retro application shell.
8
+ > **Release status:** release candidate `mop-agent@0.1.13` contains the corrected VPS
9
+ > installer, one-time Admin setup/login flow, and simplified shared application shell
10
+ > with centered page titles and ChatGPT-inspired navigation.
10
11
  > The canonical installation command is exactly `npx mop-agent`.
11
12
 
12
13
  ## Current status
@@ -7,7 +7,7 @@ import { useMemoryCore } from "@/components/AppShell";
7
7
  type Turn = { role: "user" | "assistant"; content: string };
8
8
 
9
9
  export default function AssistantPage() {
10
- const { selectedProject, setSelectedProject, projects, provider } = useMemoryCore();
10
+ const { projects } = useMemoryCore();
11
11
  const [turns, setTurns] = useState<Turn[]>([]);
12
12
  const [name, setName] = useState("Admin");
13
13
  const [input, setInput] = useState("");
@@ -34,8 +34,7 @@ export default function AssistantPage() {
34
34
  headers: { "content-type": "application/json" },
35
35
  body: JSON.stringify({
36
36
  message,
37
- projectId: selectedProject || undefined,
38
- allowCrossProject: !selectedProject,
37
+ allowCrossProject: true,
39
38
  }),
40
39
  });
41
40
 
@@ -120,14 +119,13 @@ export default function AssistantPage() {
120
119
  <button onClick={() => send()} disabled={busy || !input.trim()} style={{ ...sendButton, opacity: busy || !input.trim() ? .45 : 1 }}>↑</button>
121
120
  </div>
122
121
  <div style={{ textAlign: "center", color: "rgba(45,74,62,.62)", fontSize: 11, marginTop: 8 }}>
123
- {providerUsed ? `Answered by ${providerUsed} · ` : ""}{selectedProject ? "Selected project memory" : "Cross-project memory"}
122
+ {providerUsed ? `Answered by ${providerUsed} · ` : ""}Cross-project memory
124
123
  </div>
125
124
  </div>
126
125
  </section>
127
126
  );
128
127
  }
129
128
 
130
- const selectStyle: CSSProperties = { color: "#2d4a3e", border: "1px solid rgba(45,74,62,.42)", padding: "6px 8px", background: "#fffdf2" };
131
129
  const assistantLogo: CSSProperties = { width: 86, height: 86, display: "grid", placeItems: "center" };
132
130
  const promptGrid: CSSProperties = { width: "min(100%, 650px)", display: "grid", gridTemplateColumns: "repeat(2,minmax(0,1fr))", gap: 10, marginTop: 28 };
133
131
  const promptCard: CSSProperties = { display: "flex", justifyContent: "space-between", padding: "14px 15px", border: "1px solid rgba(45,74,62,.38)", borderBottomWidth: 3, background: "#fffdf2", color: "#2d4a3e", cursor: "pointer", textAlign: "left" };
@@ -97,7 +97,7 @@ button {
97
97
  .mop-app-frame {
98
98
  min-height: 100vh;
99
99
  display: grid;
100
- grid-template-columns: 238px minmax(0, 1fr);
100
+ grid-template-columns: 260px minmax(0, 1fr);
101
101
  grid-template-rows: 70px minmax(0, 1fr);
102
102
  grid-template-areas:
103
103
  "topbar topbar"
@@ -111,7 +111,7 @@ button {
111
111
  top: 0;
112
112
  z-index: 50;
113
113
  display: grid;
114
- grid-template-columns: 238px minmax(0, 1fr);
114
+ grid-template-columns: 260px minmax(0, 1fr);
115
115
  min-width: 0;
116
116
  color: var(--mop-cream);
117
117
  background:
@@ -153,6 +153,7 @@ button {
153
153
  min-width: 0;
154
154
  display: flex;
155
155
  align-items: center;
156
+ justify-content: center;
156
157
  gap: 14px;
157
158
  padding: 0 18px;
158
159
  }
@@ -167,18 +168,8 @@ button {
167
168
  cursor: pointer;
168
169
  }
169
170
 
170
- .mop-topbar-title {
171
- display: flex;
172
- align-items: center;
173
- gap: 8px;
174
- min-width: 130px;
175
- font-family: "SFMono-Regular", Consolas, monospace;
176
- font-size: 13px;
177
- letter-spacing: .08em;
178
- text-transform: uppercase;
179
- }
180
-
181
171
  .mop-live-dot {
172
+ flex: 0 0 auto;
182
173
  width: 8px;
183
174
  height: 8px;
184
175
  background: #78e19b;
@@ -186,37 +177,21 @@ button {
186
177
  }
187
178
 
188
179
  .mop-topbar-center {
189
- position: absolute;
190
- left: 50%;
191
- top: 50%;
192
- transform: translate(-50%, -50%);
193
- min-width: 255px;
180
+ min-width: 240px;
181
+ display: flex;
182
+ align-items: center;
183
+ justify-content: center;
184
+ gap: 10px;
194
185
  padding: 9px 28px;
195
186
  text-align: center;
196
187
  color: rgba(254, 249, 225, .86);
197
188
  border: 1px solid rgba(254, 249, 225, .12);
198
189
  background: rgba(254, 249, 225, .07);
199
190
  font-family: "SFMono-Regular", Consolas, monospace;
200
- font-size: 11px;
201
- font-weight: 800;
202
- letter-spacing: .18em;
203
- }
204
-
205
- .mop-topbar-meta {
206
- display: flex;
207
- align-items: center;
208
- gap: 9px;
209
- margin-left: auto;
210
- font-family: "SFMono-Regular", Consolas, monospace;
211
- font-size: 10px;
191
+ font-size: 12px;
212
192
  font-weight: 800;
213
- letter-spacing: .12em;
214
- }
215
-
216
- .mop-version {
217
- padding: 4px 6px;
218
- border: 1px solid rgba(254, 249, 225, .22);
219
- background: rgba(254, 249, 225, .07);
193
+ letter-spacing: .14em;
194
+ text-transform: uppercase;
220
195
  }
221
196
 
222
197
  .mop-app-sidebar {
@@ -228,53 +203,68 @@ button {
228
203
  display: flex;
229
204
  flex-direction: column;
230
205
  overflow-y: auto;
231
- padding: 15px 9px 12px;
206
+ padding: 10px 9px 9px;
232
207
  color: var(--mop-cream);
233
208
  background:
234
- linear-gradient(rgba(255,255,255,.018), rgba(0,0,0,.05)),
209
+ linear-gradient(rgba(255,255,255,.028), rgba(0,0,0,.035)),
235
210
  var(--mop-green);
236
211
  border-right: 2px solid #20362e;
237
212
  }
238
213
 
239
- .mop-nav-section { margin-bottom: 17px; }
240
- .mop-nav-section > p {
241
- margin: 0 8px 9px;
242
- color: rgba(254, 249, 225, .46);
243
- font-family: "SFMono-Regular", Consolas, monospace;
244
- font-size: 9px;
245
- font-weight: 900;
246
- letter-spacing: .22em;
214
+ .mop-sidebar-primary {
215
+ display: grid;
216
+ gap: 3px;
217
+ margin-bottom: 22px;
247
218
  }
248
219
 
249
- .mop-nav-section nav { display: grid; gap: 4px; }
250
- .mop-nav-section a {
220
+ .mop-sidebar-primary a,
221
+ .mop-nav-section a,
222
+ .mop-nav-section button {
251
223
  display: flex;
252
224
  align-items: center;
253
225
  gap: 11px;
254
- min-height: 43px;
255
- padding: 8px 12px;
256
- color: rgba(254, 249, 225, .78);
226
+ width: 100%;
227
+ min-height: 40px;
228
+ padding: 8px 11px;
229
+ color: rgba(254, 249, 225, .82);
257
230
  border: 1px solid transparent;
231
+ background: transparent;
232
+ text-align: left;
258
233
  text-decoration: none;
259
- font-family: "SFMono-Regular", Consolas, monospace;
260
- font-size: 12px;
261
- font-weight: 800;
262
- letter-spacing: .055em;
263
- text-transform: uppercase;
234
+ font-size: 14px;
235
+ font-weight: 540;
236
+ letter-spacing: 0;
237
+ cursor: pointer;
264
238
  }
265
239
 
266
- .mop-nav-section a:hover {
240
+ .mop-sidebar-primary a:hover,
241
+ .mop-nav-section a:hover,
242
+ .mop-nav-section button:hover {
267
243
  color: var(--mop-cream);
268
- background: rgba(254, 249, 225, .07);
244
+ background: rgba(254, 249, 225, .075);
245
+ }
246
+
247
+ .mop-sidebar-primary a.is-active,
248
+ .mop-nav-section a.is-active,
249
+ .mop-nav-section button.is-active {
250
+ color: var(--mop-cream);
251
+ border-color: rgba(254, 249, 225, .12);
252
+ background: rgba(254, 249, 225, .1);
253
+ box-shadow: 2px 2px 0 rgba(18, 38, 30, .22);
269
254
  }
270
255
 
271
- .mop-nav-section a.is-active {
272
- color: #ff8a3d;
273
- border-color: rgba(254, 249, 225, .18);
274
- background: rgba(254, 249, 225, .09);
275
- box-shadow: inset 3px 0 #ff6f2c, 2px 2px 0 rgba(18, 38, 30, .25);
256
+ .mop-nav-section { margin-bottom: 18px; }
257
+ .mop-nav-section > p {
258
+ margin: 0 11px 7px;
259
+ color: rgba(254, 249, 225, .46);
260
+ font-family: "SFMono-Regular", Consolas, monospace;
261
+ font-size: 10px;
262
+ font-weight: 750;
263
+ letter-spacing: .11em;
276
264
  }
277
265
 
266
+ .mop-nav-section nav { display: grid; gap: 2px; }
267
+
278
268
  .mop-nav-icon {
279
269
  width: 21px;
280
270
  text-align: center;
@@ -282,6 +272,32 @@ button {
282
272
  font-size: 16px;
283
273
  }
284
274
 
275
+ .mop-project-memory {
276
+ min-height: 0;
277
+ overflow: hidden;
278
+ }
279
+
280
+ .mop-project-memory nav {
281
+ max-height: min(34vh, 280px);
282
+ overflow-y: auto;
283
+ scrollbar-width: thin;
284
+ }
285
+
286
+ .mop-project-memory a { min-height: 34px; padding-block: 5px; font-size: 13px; }
287
+ .mop-project-dot {
288
+ flex: 0 0 auto;
289
+ width: 7px;
290
+ height: 7px;
291
+ background: rgba(254, 249, 225, .34);
292
+ }
293
+ .mop-project-dot[data-online="true"] { background: #78e19b; box-shadow: 0 0 0 2px rgba(120, 225, 155, .12); }
294
+ .mop-nav-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
295
+ .mop-sidebar-empty { display: block; padding: 7px 11px; color: rgba(254, 249, 225, .42); font-size: 12px; }
296
+ .mop-admin-nav { margin-top: 2px; }
297
+ .mop-settings-subnav { display: grid; gap: 1px; margin: 1px 0 2px 31px; border-left: 1px solid rgba(254, 249, 225, .18); }
298
+ .mop-settings-subnav button { min-height: 32px; padding: 5px 12px; color: rgba(254, 249, 225, .6); font-size: 12px; }
299
+ .mop-settings-subnav button.is-active { color: #ff9a56; border-color: transparent; background: transparent; box-shadow: none; }
300
+
285
301
  .mop-sidebar-spacer { flex: 1; }
286
302
  .mop-account-card {
287
303
  width: 100%;
@@ -290,11 +306,12 @@ button {
290
306
  gap: 9px;
291
307
  padding: 10px 8px;
292
308
  color: var(--mop-cream);
293
- border: 1px solid rgba(254, 249, 225, .13);
294
- background: rgba(0, 0, 0, .07);
309
+ border: 1px solid transparent;
310
+ background: transparent;
295
311
  text-align: left;
296
312
  cursor: pointer;
297
313
  }
314
+ .mop-account-card:hover { border-color: rgba(254, 249, 225, .1); background: rgba(254, 249, 225, .07); }
298
315
 
299
316
  .mop-account-avatar {
300
317
  flex: 0 0 auto;
@@ -378,25 +395,16 @@ button {
378
395
  flex-direction: column;
379
396
  }
380
397
 
381
- .mop-assistant-toolbar {
382
- min-height: 55px;
383
- display: flex;
384
- align-items: center;
385
- justify-content: space-between;
386
- gap: 16px;
387
- padding: 9px 22px;
388
- border-bottom: 1px solid rgba(45, 74, 62, .24);
389
- background: rgba(254, 249, 225, .78);
390
- }
391
-
392
- .mop-assistant-conversation { flex: 1; overflow-y: auto; padding: 0 28px; }
398
+ .mop-assistant-conversation { flex: 1; overflow-y: auto; padding: 0 clamp(18px, 5vw, 64px); }
393
399
  .mop-assistant-welcome {
394
- min-height: calc(100vh - 285px);
400
+ width: min(100%, 760px);
401
+ min-height: calc(100vh - 190px);
402
+ margin: 0 auto;
395
403
  display: flex;
396
404
  flex-direction: column;
397
405
  align-items: center;
398
406
  justify-content: center;
399
- padding: 32px 0 90px;
407
+ padding: 38px 0 110px;
400
408
  text-align: center;
401
409
  }
402
410
 
@@ -417,26 +425,6 @@ button {
417
425
  }
418
426
 
419
427
 
420
- .mop-settings-nav { padding: 9px; }
421
- .mop-settings-nav button {
422
- width: 100%;
423
- display: flex;
424
- align-items: center;
425
- gap: 10px;
426
- padding: 12px;
427
- border: 1px solid transparent;
428
- background: transparent;
429
- color: var(--mop-green);
430
- text-align: left;
431
- cursor: pointer;
432
- }
433
-
434
- .mop-settings-nav button.is-active {
435
- color: var(--mop-cream);
436
- border-color: var(--mop-red);
437
- background: var(--mop-red);
438
- }
439
-
440
428
  .mop-settings-content { padding: clamp(18px, 3vw, 30px); }
441
429
  .mop-settings-content th,
442
430
  .mop-settings-content td {
@@ -472,11 +460,10 @@ button {
472
460
  .mop-app-topbar { grid-template-columns: 66px minmax(0, 1fr); }
473
461
  .mop-app-brand { justify-content: center; padding: 5px; }
474
462
  .mop-app-brand img { width: 49px; height: 49px; }
475
- .mop-app-brand span, .mop-topbar-center { display: none; }
463
+ .mop-app-brand span { display: none; }
476
464
  .mop-app-topbar-main { padding: 0 10px; gap: 9px; }
477
465
  .mop-menu-toggle { display: block; }
478
- .mop-topbar-title { min-width: 0; font-size: 11px; }
479
- .mop-topbar-meta > span:first-child { display: none; }
466
+ .mop-topbar-center { min-width: 0; flex: 1; padding: 8px 12px; font-size: 10px; }
480
467
  .mop-app-sidebar {
481
468
  position: fixed;
482
469
  top: 62px;
@@ -498,72 +485,8 @@ button {
498
485
  }
499
486
  .mop-app-main { min-height: calc(100vh - 62px); }
500
487
  .mop-assistant-page { min-height: calc(100vh - 62px); }
501
- .mop-assistant-toolbar { align-items: flex-start; flex-direction: column; }
502
488
  .mop-assistant-conversation { padding: 0 16px; }
503
489
  .mop-assistant-composer-wrap { padding: 26px 12px 12px; }
504
490
  .mop-settings-grid { grid-template-columns: 1fr; }
505
- .mop-settings-nav { display: flex; gap: 7px; }
506
- .mop-settings-nav button { justify-content: center; }
507
491
  .mop-user-invite-form { grid-template-columns: 1fr !important; }
508
- .mop-settings-sidebar {
509
- position: fixed;
510
- top: 62px;
511
- left: 0;
512
- bottom: 0;
513
- width: min(82vw, 280px);
514
- height: auto;
515
- transform: translateX(-105%);
516
- transition: transform 160ms steps(4, end);
517
- display: flex !important;
518
- flex-direction: column !important;
519
- }
520
- .mop-settings-sidebar.is-open {
521
- transform: translateX(0);
522
- }
523
492
  }
524
-
525
- .mop-settings-sidebar {
526
- grid-area: sidebar;
527
- position: sticky;
528
- top: 70px;
529
- height: calc(100vh - 70px);
530
- z-index: 40;
531
- display: flex;
532
- flex-direction: column;
533
- overflow-y: auto;
534
- padding: 15px 9px 12px;
535
- border-right: 2px solid rgba(45, 74, 62, .46);
536
- }
537
-
538
- .mop-back-workspace-btn {
539
- display: flex;
540
- align-items: center;
541
- justify-content: center;
542
- gap: 8px;
543
- width: 100%;
544
- min-height: 40px;
545
- margin-bottom: 9px;
546
- padding: 9px 12px;
547
- border: 1px solid var(--mop-red);
548
- background: var(--mop-red);
549
- color: var(--mop-cream);
550
- font-family: "SFMono-Regular", Consolas, monospace;
551
- font-size: 11px;
552
- font-weight: 900;
553
- text-decoration: none;
554
- cursor: pointer;
555
- transition: transform 80ms steps(2, end), box-shadow 80ms steps(2, end);
556
- box-shadow: 2px 2px 0 rgba(45, 74, 62, .17);
557
- }
558
-
559
- .mop-back-workspace-btn:hover {
560
- transform: translate(-1px, -1px);
561
- box-shadow: 3px 3px 0 rgba(45, 74, 62, .24);
562
- color: var(--mop-cream);
563
- }
564
-
565
- .mop-back-workspace-btn:active {
566
- transform: translate(1px, 1px);
567
- box-shadow: 0 0 0 rgba(45, 74, 62, 0);
568
- }
569
-
@@ -12,13 +12,9 @@ export type AppViewer = {
12
12
  };
13
13
 
14
14
  export type Project = { id: string; name: string; status: string };
15
- export type ProviderState = { configured: boolean; provider?: string; model?: string | null };
16
15
 
17
16
  interface MemoryCoreContextType {
18
- selectedProject: string;
19
- setSelectedProject: (id: string) => void;
20
17
  projects: Project[];
21
- provider: ProviderState;
22
18
  settingsSection: "providers" | "users";
23
19
  setSettingsSection: (section: "providers" | "users") => void;
24
20
  }
@@ -27,15 +23,10 @@ const MemoryCoreContext = createContext<MemoryCoreContextType | undefined>(undef
27
23
 
28
24
  export function useMemoryCore() {
29
25
  const context = useContext(MemoryCoreContext);
30
- if (!context) {
31
- throw new Error("useMemoryCore must be used within a MemoryCoreProvider");
32
- }
26
+ if (!context) throw new Error("useMemoryCore must be used within a MemoryCoreProvider");
33
27
  return context;
34
28
  }
35
29
 
36
- const selectStyle = { color: "#2d4a3e", border: "1px solid rgba(45,74,62,.42)", padding: "6px 8px", background: "#fffdf2" };
37
-
38
-
39
30
  function pageTitle(pathname: string): string {
40
31
  if (pathname === "/assistant") return "Assistant";
41
32
  if (pathname === "/brain/graph") return "Knowledge Graph";
@@ -49,22 +40,17 @@ function pageTitle(pathname: string): string {
49
40
  export function AppShell({ viewer, children }: { viewer: AppViewer; children: ReactNode }) {
50
41
  const pathname = usePathname();
51
42
  const [menuOpen, setMenuOpen] = useState(false);
52
- const isAdmin = viewer.role === "owner";
53
- const title = pageTitle(pathname);
54
-
55
43
  const [projects, setProjects] = useState<Project[]>([]);
56
- const [provider, setProvider] = useState<ProviderState>({ configured: false });
57
- const [selectedProject, setSelectedProject] = useState("");
58
44
  const [settingsSection, setSettingsSection] = useState<"providers" | "users">("providers");
45
+ const isAdmin = viewer.role === "owner";
46
+ const isSettings = pathname.startsWith("/settings");
47
+ const title = pageTitle(pathname);
59
48
 
60
49
  useEffect(() => {
61
- Promise.all([
62
- fetch("/api/projects").then((r) => r.json()),
63
- fetch("/api/providers").then((r) => r.json()),
64
- ]).then(([projectData, providerData]) => {
65
- setProjects(projectData.projects ?? []);
66
- setProvider(providerData.config ?? { configured: false });
67
- }).catch(() => {});
50
+ fetch("/api/projects")
51
+ .then((response) => response.json())
52
+ .then((data) => setProjects(data.projects ?? []))
53
+ .catch(() => {});
68
54
 
69
55
  const requested = new URLSearchParams(window.location.search).get("section");
70
56
  if (requested === "users") setSettingsSection("users");
@@ -75,21 +61,13 @@ export function AppShell({ viewer, children }: { viewer: AppViewer; children: Re
75
61
  window.location.replace("/login");
76
62
  }
77
63
 
78
- const selectSection = (sec: "providers" | "users") => {
79
- setSettingsSection(sec);
80
- const url = sec === "providers" ? "/settings" : "/settings?section=users";
81
- window.history.replaceState(null, "", url);
82
- };
83
-
84
- const isSettings = pathname.startsWith("/settings");
85
-
86
- const nav = [
87
- { href: "/assistant", label: "Assistant", icon: "✦", active: pathname.startsWith("/assistant") || pathname.startsWith("/chat/") },
88
- { href: "/brain", label: "Brain", icon: "◉", active: pathname.startsWith("/brain") },
89
- ];
64
+ function selectSection(section: "providers" | "users") {
65
+ setSettingsSection(section);
66
+ window.history.replaceState(null, "", section === "providers" ? "/settings" : "/settings?section=users");
67
+ }
90
68
 
91
69
  return (
92
- <MemoryCoreContext.Provider value={{ selectedProject, setSelectedProject, projects, provider, settingsSection, setSettingsSection }}>
70
+ <MemoryCoreContext.Provider value={{ projects, settingsSection, setSettingsSection }}>
93
71
  <div className="mop-app-frame">
94
72
  <header className="mop-app-topbar">
95
73
  <a className="mop-app-brand" href="/assistant" aria-label="MOP-AGENT home">
@@ -106,96 +84,70 @@ export function AppShell({ viewer, children }: { viewer: AppViewer; children: Re
106
84
  >
107
85
 
108
86
  </button>
109
- {pathname === "/assistant" ? (
110
- <div className="mop-assistant-toolbar" style={{ border: 0, padding: 0, margin: 0, background: "transparent", width: "100%", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
111
- <div>
112
- <strong style={{ fontFamily: '"SFMono-Regular", Consolas, monospace', color: "#742220" }}>LIVE ASSISTANT</strong>
113
- <span style={{ color: "rgba(45,74,62,.62)", marginLeft: 10, fontSize: 12 }}>
114
- {provider.configured ? `${provider.provider}${provider.model ? ` · ${provider.model}` : ""}` : "offline demo"}
115
- </span>
116
- </div>
117
- <label style={{ color: "#2d4a3e", fontSize: 12 }}>
118
- MEMORY SCOPE&nbsp;
119
- <select value={selectedProject} onChange={(e) => setSelectedProject(e.target.value)} style={selectStyle}>
120
- <option value="">All memory</option>
121
- {projects.map((project) => <option key={project.id} value={project.id}>{project.name}</option>)}
122
- </select>
123
- </label>
124
- </div>
125
- ) : (
126
- <>
127
- <div className="mop-topbar-title">
128
- <span className="mop-live-dot" />
129
- <strong>{title}</strong>
130
- </div>
131
- <div className="mop-topbar-center">MOP MEMORYCORE</div>
132
- <div className="mop-topbar-meta">
133
- <span>{isAdmin ? "ADMIN" : "MEMBER"}</span>
134
- <span className="mop-version">v0.1.10</span>
135
- </div>
136
- </>
137
- )}
87
+ <div className="mop-topbar-center">
88
+ <span className="mop-live-dot" />
89
+ <strong>{title}</strong>
90
+ </div>
138
91
  </div>
139
92
  </header>
140
93
 
141
94
  {menuOpen && <button className="mop-sidebar-scrim" aria-label="Close navigation" onClick={() => setMenuOpen(false)} />}
142
95
 
143
- <aside className={isSettings
144
- ? `mop-settings-nav mop-panel mop-settings-sidebar${menuOpen ? " is-open" : ""}`
145
- : `mop-app-sidebar${menuOpen ? " is-open" : ""}`}
146
- >
147
- {isSettings ? (
148
- <>
149
- <button className={settingsSection === "providers" ? "is-active" : ""} onClick={() => { selectSection("providers"); setMenuOpen(false); }}>
150
- <span>◇</span><strong>Providers</strong>
151
- </button>
152
- <button className={settingsSection === "users" ? "is-active" : ""} onClick={() => { selectSection("users"); setMenuOpen(false); }}>
153
- <span>♙</span><strong>Users</strong>
154
- </button>
155
- </>
156
- ) : (
157
- <>
158
- <div className="mop-nav-section">
159
- <p>WORKSPACE</p>
160
- <nav>
161
- {nav.map((item) => (
162
- <a key={item.href} href={item.href} className={item.active ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
163
- <span className="mop-nav-icon">{item.icon}</span>
164
- <span>{item.label}</span>
165
- </a>
166
- ))}
167
- </nav>
168
- </div>
169
-
170
- {isAdmin && (
171
- <div className="mop-nav-section">
172
- <p>ADMIN</p>
173
- <nav>
174
- <a href="/settings" className={pathname.startsWith("/settings") ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
175
- <span className="mop-nav-icon">⚙</span>
176
- <span>Settings</span>
177
- </a>
178
- </nav>
179
- </div>
180
- )}
181
- </>
182
- )}
183
-
184
- <div className="mop-sidebar-spacer" />
185
-
186
- {isSettings && (
187
- <a href="/assistant" className="mop-back-workspace-btn">
188
- <span>← BACK TO WORKSPACE</span>
96
+ <aside className={`mop-app-sidebar${menuOpen ? " is-open" : ""}`}>
97
+ <nav className="mop-sidebar-primary" aria-label="Workspace">
98
+ <a href="/assistant" className={pathname.startsWith("/assistant") || pathname.startsWith("/chat/") ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
99
+ <span className="mop-nav-icon">✎</span>
100
+ <span>New chat</span>
189
101
  </a>
102
+ <a href="/brain" className={pathname.startsWith("/brain") ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
103
+ <span className="mop-nav-icon">◉</span>
104
+ <span>Brain</span>
105
+ </a>
106
+ </nav>
107
+
108
+ <div className="mop-nav-section mop-project-memory">
109
+ <p>PROJECT MEMORY</p>
110
+ <nav>
111
+ {projects.slice(0, 8).map((project) => (
112
+ <a key={project.id} href={`/brain/${project.id}`} className={pathname === `/brain/${project.id}` ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
113
+ <span className="mop-project-dot" data-online={project.status === "online"} />
114
+ <span className="mop-nav-label">{project.name}</span>
115
+ </a>
116
+ ))}
117
+ {projects.length === 0 && <span className="mop-sidebar-empty">No linked projects yet</span>}
118
+ </nav>
119
+ </div>
120
+
121
+ {isAdmin && (
122
+ <div className="mop-nav-section mop-admin-nav">
123
+ <p>ADMIN</p>
124
+ <nav>
125
+ <a href="/settings" className={isSettings ? "is-active" : ""} onClick={() => setMenuOpen(false)}>
126
+ <span className="mop-nav-icon">⚙</span>
127
+ <span>Settings</span>
128
+ </a>
129
+ {isSettings && (
130
+ <div className="mop-settings-subnav">
131
+ <button className={settingsSection === "providers" ? "is-active" : ""} onClick={() => { selectSection("providers"); setMenuOpen(false); }}>
132
+ <span>Providers</span>
133
+ </button>
134
+ <button className={settingsSection === "users" ? "is-active" : ""} onClick={() => { selectSection("users"); setMenuOpen(false); }}>
135
+ <span>Users</span>
136
+ </button>
137
+ </div>
138
+ )}
139
+ </nav>
140
+ </div>
190
141
  )}
191
142
 
143
+ <div className="mop-sidebar-spacer" />
192
144
  <button className="mop-account-card" type="button" onClick={logout} title="Sign out">
193
145
  <span className="mop-account-avatar">{viewer.name.slice(0, 1).toUpperCase()}</span>
194
146
  <span className="mop-account-copy">
195
147
  <strong>{viewer.name}</strong>
196
148
  <small>{isAdmin ? "Administrator" : "Member"}</small>
197
149
  </span>
198
- <span aria-hidden="true">↪</span>
150
+ <span aria-hidden="true">•••</span>
199
151
  </button>
200
152
  </aside>
201
153
 
@@ -204,4 +156,3 @@ export function AppShell({ viewer, children }: { viewer: AppViewer; children: Re
204
156
  </MemoryCoreContext.Provider>
205
157
  );
206
158
  }
207
-
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "mop-agent",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "mop-agent",
9
- "version": "0.1.11",
9
+ "version": "0.1.13",
10
10
  "license": "UNLICENSED",
11
11
  "workspaces": [
12
12
  "packages/*",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mop-agent",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Self-hosted AI assistant with persistent cross-project memory, installed with npx mop-agent.",
5
5
  "author": "BURHANDEV ENTERPRISE",
6
6
  "license": "UNLICENSED",