From 3b2c393db38c5432e753b86ac6b5ea75cc0bd9fc Mon Sep 17 00:00:00 2001
From: KT <677465+kevintseng@users.noreply.github.com>
Date: Sun, 16 Aug 2026 19:55:33 +0800
Subject: [PATCH 1/2] =?UTF-8?q?feat(dashboard):=20one=20notice=20at=20a=20?=
=?UTF-8?q?time=20=E2=80=94=20the=20banner=20wall=20becomes=20a=20priority?=
=?UTF-8?q?=20slot?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Doctor, Onboarding and Insights banners could stack three deep above the
nav. They now share a notice slot: each banner still decides its own
eligibility (an ineligible banner renders no DOM), App orders them by
priority — Doctor (broken install) > Onboarding (empty library) >
Insights (pending proposals) — and one stylesheet rule shows only the
slot's first rendered child, keeping the losers out of the viewport AND
the accessibility tree until the winner clears. A dedicated test pins
both halves of the mechanism: the DOM priority order (including the
next-in-line takeover when the winner is dismissed) and the CSS rule
itself, which happy-dom cannot observe at runtime.
---
dashboard/src/App.tsx | 15 +++-
dashboard/src/styles/global.css | 10 +++
tests/dashboard/notice-slot.test.tsx | 117 +++++++++++++++++++++++++++
3 files changed, 139 insertions(+), 3 deletions(-)
create mode 100644 tests/dashboard/notice-slot.test.tsx
diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx
index 27948fa7..10a22b5a 100644
--- a/dashboard/src/App.tsx
+++ b/dashboard/src/App.tsx
@@ -167,9 +167,18 @@ export function App() {
return (
-
-
setTab('Home')} />
-
+ {/* The notice slot: one banner at a time. Each banner self-decides
+ eligibility (ineligible = no DOM), and DOM order IS the priority —
+ Doctor (broken install) > Onboarding (empty library) > Insights
+ (pending proposals). The stylesheet shows only the slot's first
+ rendered child; the rest wait in the tree for the winner to clear
+ (dismissal or the condition resolving). Three banners could
+ previously stack into a wall above the nav. */}
+
+
+
+ setTab('Home')} />
+
setTab(k as Tab)} />
{/* Each panel is the tabpanel for its TabNav tab: id + role +
aria-labelledby wire the roving-tablist relationship (see TabNav). */}
diff --git a/dashboard/src/styles/global.css b/dashboard/src/styles/global.css
index 80fc6da3..a9040394 100644
--- a/dashboard/src/styles/global.css
+++ b/dashboard/src/styles/global.css
@@ -155,6 +155,16 @@ body {
}
.theme-btn:hover { border-color: var(--text-3); }
+/* ---- Notice slot ---- */
+/* One notice at a time. The banners inside self-decide eligibility (an
+ ineligible banner renders no DOM node), so the slot's FIRST rendered
+ child is the highest-priority applicable notice — App.tsx orders them
+ Doctor > Onboarding > Insights. display:none keeps the losers out of
+ the accessibility tree as well as the viewport; they surface the
+ moment the winner clears. tests/dashboard/notice-slot.test.tsx pins
+ both this rule and the DOM order. */
+.notice-slot > * ~ * { display: none; }
+
/* Nav */
.nav {
display: flex;
diff --git a/tests/dashboard/notice-slot.test.tsx b/tests/dashboard/notice-slot.test.tsx
new file mode 100644
index 00000000..9ce686d0
--- /dev/null
+++ b/tests/dashboard/notice-slot.test.tsx
@@ -0,0 +1,117 @@
+// @vitest-environment happy-dom
+//
+// The notice slot: at most ONE banner interrupts at a time, by priority
+// Doctor > Onboarding > Insights. The mechanism is split across two
+// places, so this file pins both halves:
+//
+// 1. App.tsx renders the three banners inside `.notice-slot` in priority
+// order — each banner self-decides eligibility and renders no DOM
+// when ineligible, so document order IS the priority order.
+// 2. global.css hides every slot child after the first rendered one.
+// happy-dom does not compute stylesheet cascade, so the rule is
+// pinned at the source: delete or loosen it and this file goes red
+// even though every DOM assertion would still pass.
+//
+// All network is stubbed — nothing here touches ~/.memesh or any config.
+
+import { describe, it, expect, afterEach, beforeEach, vi } from 'vitest';
+import { render, waitFor } from '@testing-library/preact';
+import { readFileSync } from 'fs';
+import { t } from '../../dashboard/src/lib/i18n';
+import { App } from '../../dashboard/src/App';
+
+function jsonResponse(body: unknown): Response {
+ return new Response(JSON.stringify(body), {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ });
+}
+
+/** Every banner eligible at once: doctor FAILs, the library is empty, and
+ * two dream proposals wait. (Insights additionally needs the active tab
+ * to not be Home — pinned via the stored tab below.) */
+function stubAllBannersEligible() {
+ vi.spyOn(globalThis, 'fetch').mockImplementation(async (input: RequestInfo | URL) => {
+ const url = String(input);
+ if (url.includes('/v1/doctor')) {
+ return jsonResponse({
+ success: true,
+ data: {
+ status: 'FAIL',
+ checks: [{ id: 'db', label: 'Database', status: 'fail', summary: 'db unreadable' }],
+ },
+ });
+ }
+ if (url.includes('/v1/dream/proposals')) {
+ return jsonResponse({ success: true, data: [{ id: 1, status: 'pending' }, { id: 2, status: 'pending' }] });
+ }
+ if (url.includes('/v1/health')) {
+ return jsonResponse({ success: true, data: { status: 'ok', version: 't', entity_count: 0 } });
+ }
+ if (url.includes('/v1/config')) {
+ return jsonResponse({ success: true, data: {} });
+ }
+ return jsonResponse({ success: true, data: [] });
+ });
+}
+
+describe('the notice slot shows one banner at a time, by priority', () => {
+ beforeEach(() => {
+ localStorage.clear();
+ sessionStorage.clear();
+ // Insights self-suppresses on Home (its content lives there); park the
+ // app on Memories so all three banners are eligible simultaneously.
+ localStorage.setItem('memesh.tab', 'Memories');
+ });
+ afterEach(() => {
+ vi.restoreAllMocks();
+ localStorage.clear();
+ sessionStorage.clear();
+ });
+
+ it('renders all eligible banners inside the slot in Doctor > Onboarding > Insights order', async () => {
+ stubAllBannersEligible();
+ const { container } = render();
+
+ const slot = container.querySelector('.notice-slot');
+ expect(slot, 'App must render the notice slot').not.toBeNull();
+
+ await waitFor(() => {
+ // All three eligible banners have landed in the slot.
+ expect(slot!.children.length).toBe(3);
+ });
+
+ // Document order is the priority order — the stylesheet shows only the
+ // first child, so getting this order wrong silently changes which
+ // notice the user sees.
+ const [first, second, third] = [...slot!.children];
+ expect(first.textContent).toContain('db unreadable');
+ expect(second.textContent).toContain(t('onboarding.title'));
+ expect(third.textContent).toContain(
+ t('banner.pendingInsights', { n: 2, s: 's' }),
+ );
+ });
+
+ it('the next notice in line takes the slot when the winner is not eligible', async () => {
+ stubAllBannersEligible();
+ // The doctor banner's dismissal signature matches the stubbed failing
+ // check, so the highest-priority notice is out of the running from the
+ // first render — Onboarding must be the slot's first child.
+ localStorage.setItem('memesh.doctorBanner.dismissedSig', 'db:fail::');
+ const { container } = render();
+
+ const slot = container.querySelector('.notice-slot')!;
+ await waitFor(() => {
+ expect(slot.children.length).toBe(2);
+ });
+ expect(slot.children[0].textContent).toContain(t('onboarding.title'));
+ });
+
+ it('the stylesheet hides every slot child after the first', () => {
+ const css = readFileSync('dashboard/src/styles/global.css', 'utf8');
+ // The one-notice rule: any .notice-slot child with a preceding sibling
+ // is display:none. Whitespace-tolerant, but the selector and the
+ // declaration must both survive.
+ expect(css).toMatch(/\.notice-slot\s*>\s*\*\s*~\s*\*\s*\{\s*display:\s*none;?\s*\}/);
+ });
+});
From 4a162080aeca53adcfd6fffd8c1c8aefef12c69a Mon Sep 17 00:00:00 2001
From: KT <677465+kevintseng@users.noreply.github.com>
Date: Sun, 16 Aug 2026 19:56:28 +0800
Subject: [PATCH 2/2] docs(changelog): the notice slot; regenerate the packaged
bundle
---
CHANGELOG.md | 5 +++++
dashboard/dist/index.html | 4 ++--
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 521ed82c..5f4c84ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -47,6 +47,11 @@ All notable changes to MeMesh are documented here.
its own **Project** tab. Old `?tab=` deep links and stored tab choices
migrate to the surface that absorbed them, so a bookmarked
`?tab=Browse` opens Memories instead of nothing.
+- **One notice at a time.** The Doctor, Onboarding and Insights banners
+ could stack three deep above the nav. They now share a priority slot —
+ Doctor (broken install) > Onboarding (empty library) > Insights
+ (pending proposals) — showing exactly one; the next in line surfaces
+ the moment the winner is dismissed or its condition clears.
## [4.6.0] — 2026-08-16
diff --git a/dashboard/dist/index.html b/dashboard/dist/index.html
index 1684cbb5..93219d80 100644
--- a/dashboard/dist/index.html
+++ b/dashboard/dist/index.html
@@ -26,8 +26,8 @@
${v(F)}`}catch{}}const P=n(co[i]),B=`https://github.com/PCIRCLE-AI/memesh/issues/new?title=${encodeURIComponent(`[${P}] `)}&body=${encodeURIComponent(S)}&labels=${encodeURIComponent(f)}`;window.open(B,"_blank"),u(""),o(!1)}finally{g(!1)}}};return e(Z,{children:[e("button",{ref:b,class:"fb-btn",onClick:()=>o(!s),"aria-haspopup":"dialog","aria-expanded":s,"aria-controls":"fb-panel",children:n("feedback.button")}),s&&e("div",{class:"fb-panel",ref:l,role:"dialog",id:"fb-panel","aria-labelledby":"fb-title",children:[e("h3",{class:"fb-title",id:"fb-title",children:n("feedback.title")}),e("div",{class:"fb-types",children:Yr.map(f=>e("label",{class:`fb-type ${i===f?"selected":""}`,children:[e("input",{type:"radio",name:"fb-type",value:f,checked:i===f,onChange:()=>r(f)}),n(co[f])]},f))}),e("textarea",{ref:y,class:"fb-desc","aria-label":n("feedback.descLabel"),placeholder:n("feedback.placeholder"),value:a,onInput:f=>u(f.target.value)}),e("label",{class:"fb-sys-row",children:[e("input",{type:"checkbox",checked:m,onChange:()=>c(!m)}),n("feedback.includeSys")]}),e("button",{class:"btn btn-primary fb-submit",onClick:w,disabled:d,children:n(d?"feedback.submitting":"feedback.submit")})]})]})}function Jr({currentToken:t,onSubmit:s,rejected:o=!1}){const[i,r]=M(t??""),[a,u]=M(!1);function m(l){l.preventDefault(),u(!0),i.trim()&&s(i.trim())}const c=a&&!i.trim(),d=o&&!c,g=c||d?"auth-prompt-error":void 0;return e("div",{class:"auth-prompt-shell","data-testid":"auth-prompt",children:e("form",{class:"auth-prompt-card",onSubmit:m,children:[e("h1",{children:n("auth.title")}),e("p",{children:n("auth.intro")}),e("label",{children:[e("span",{children:n("auth.tokenLabel")}),e("input",{type:"password",autocomplete:"off",value:i,onInput:l=>r(l.target.value),placeholder:n("auth.tokenPlaceholder"),"aria-invalid":g!==void 0,"aria-describedby":g,autofocus:!0})]}),c&&e("p",{class:"auth-prompt-error",id:g,role:"alert",children:n("auth.empty")}),d&&e("p",{class:"auth-prompt-error",id:g,role:"alert",children:n("auth.invalid")}),e("button",{type:"submit",class:"auth-prompt-submit",children:n("auth.submit")})]})})}const mo="memesh.onboardingDismissed";function Qr({health:t}){const[s,o]=M(()=>{try{return localStorage.getItem(mo)==="true"}catch{return!1}}),[i,r]=M(null),[a,u]=M("");if(ee(()=>{t&&t.entity_count>0&&o(!0)},[t?.entity_count]),!t||t.entity_count>0||s)return null;function m(){o(!0);try{localStorage.setItem(mo,"true")}catch{}}async function c(){u(""),r("seed");try{await Y("POST","/v1/demo/seed"),window.dispatchEvent(new Event("memesh:data-changed"))}catch(g){u(Se(g))}finally{r(null)}}async function d(){if(confirm(n("onboarding.resetConfirm"))){u(""),r("reset");try{await Y("POST","/v1/demo/reset"),window.dispatchEvent(new Event("memesh:data-changed"))}catch(g){u(Se(g))}finally{r(null)}}}return e("div",{role:"region","aria-label":n("onboarding.title"),style:{position:"relative",margin:"12px auto 8px",maxWidth:920,padding:"14px 18px",border:"1px solid rgba(143, 242, 92, 0.28)",borderRadius:"var(--radius)",background:"var(--life-soft)",color:"var(--text-1)"},children:[e("button",{type:"button",onClick:m,"aria-label":n("onboarding.dismiss"),style:{position:"absolute",top:8,right:10,background:"transparent",border:"none",color:"var(--text-3)",fontSize:18,lineHeight:1,cursor:"pointer",padding:4},children:"×"}),e("div",{style:{fontSize:14,fontWeight:600,color:"var(--text-0)",marginBottom:6},children:n("onboarding.title")}),e("div",{style:{fontSize:13,lineHeight:1.55,color:"var(--text-1)"},children:n("onboarding.body")}),e("div",{style:{fontSize:11,lineHeight:1.55,color:"var(--text-3)",marginTop:4},children:n("onboarding.llmHint")}),e("div",{style:{display:"flex",flexWrap:"wrap",gap:10,marginTop:12,alignItems:"center"},children:[e("button",{type:"button",class:"btn btn-primary",onClick:c,disabled:i!==null,style:{minWidth:160},children:n(i==="seed"?"onboarding.seedingButton":"onboarding.seedButton")}),e("span",{style:{fontSize:12,color:"var(--text-3)"},children:n("onboarding.seedHint")})]}),e("details",{style:{marginTop:10,fontSize:12,color:"var(--text-3)"},children:[e("summary",{style:{cursor:"pointer"},children:n("onboarding.cliReference")}),e("div",{style:{display:"flex",flexWrap:"wrap",gap:12,marginTop:8},children:[e("code",{style:{padding:"6px 10px",background:"rgba(0, 0, 0, 0.3)",border:"1px solid rgba(143, 242, 92, 0.20)",borderRadius:"var(--radius-xs)",color:"var(--life)",fontFamily:"var(--mono)",fontSize:12},children:"memesh demo"}),e("span",{style:{fontSize:12,color:"var(--text-3)"},children:n("onboarding.hintDemo")})]}),e("div",{style:{display:"flex",flexWrap:"wrap",gap:12,marginTop:8,alignItems:"center"},children:[e("code",{style:{padding:"6px 10px",background:"rgba(0, 0, 0, 0.3)",border:"1px solid var(--border-subtle)",borderRadius:"var(--radius-xs)",color:"var(--text-2)",fontFamily:"var(--mono)",fontSize:12},children:"memesh demo --reset --yes"}),e("button",{type:"button",class:"btn",onClick:d,disabled:i!==null,style:{fontSize:11,padding:"4px 10px"},children:n(i==="reset"?"onboarding.resettingButton":"onboarding.resetButton")}),e("span",{style:{fontSize:12,color:"var(--text-3)"},children:n("onboarding.hintReset")})]})]}),a&&e("div",{role:"alert",style:{marginTop:10,fontSize:12,color:"var(--danger)"},children:a})]})}const uo="memesh.doctorBanner.dismissedSig";function bs(t,s,o){const i=n(t,o);return i===t?s:i}function Zr(t){return t.code?bs(`doctor.msg.${t.code}.summary`,t.summary,t.params):t.summary}function ea(t){if(t.fix)return t.code?bs(`doctor.msg.${t.code}.fix`,t.fix,t.params):t.fix}function ta(t){return bs(`doctor.label.${t.id}`,t.label)}const sa=new Set(["update-status.no-cache","update-status.stale","update-status.deprecation-unknown","hook-activity.not-wired","shell-cli.not-on-path","skills-manifest.missing-dev","install-channel.unknown","http-probe.no-server","readme-parity.unreadable","readme-parity.drift"]);function oa(t){if(t.status==="fail")return!0;if(t.status!=="warn"||t.code&&sa.has(t.code)||!t.fix)return!1;const s=t.fix.trim().toLowerCase();return!(!s||s==="no action needed"||s.startsWith("no action"))}function na(){const[t,s]=M(null),[o,i]=M(()=>{try{return localStorage.getItem(uo)??""}catch{return""}});if(ee(()=>{let b=!0;const y=()=>{Y("GET","/v1/doctor").then(v=>{b&&s(Array.isArray(v?.checks)?v:null)}).catch(()=>{})};y();const h=()=>y();return window.addEventListener("memesh:data-changed",h),()=>{b=!1,window.removeEventListener("memesh:data-changed",h)}},[]),!t||t.status==="PASS")return null;const r=t.checks.filter(oa);if(r.length===0)return null;const a=r.map(b=>`${b.id}:${b.status}:${b.code??""}:${b.params?.hook??""}`).sort().join("|");if(a===o)return null;function u(){i(a);try{localStorage.setItem(uo,a)}catch{}}function m(){const b=r.map(w=>{const f=w.status==="fail"?"❌":"⚠️",S=w.fix?` _Fix: ${w.fix}_`:"";return`- ${f} **${w.label}**: ${w.summary}${S}`}),y=`${n("doctorBanner.preambleForIssue")}
${b.join(`
-`)}`,v=`https://github.com/PCIRCLE-AI/memesh/issues/new?title=${encodeURIComponent("[Bug] memesh doctor reported issues")}&body=${encodeURIComponent(y)}&labels=${encodeURIComponent("feedback,from-dashboard,bug,doctor-warning")}`;window.open(v,"_blank")}const c=t.status==="FAIL",d=c?"var(--danger)":"var(--warning)",g=c?"var(--danger-soft)":"var(--warning-soft)";return e("div",{role:"alert",style:{position:"relative",margin:"12px auto 0",maxWidth:920,padding:"12px 16px",border:`1px solid ${d}`,borderRadius:"var(--radius)",background:g,color:"var(--text-1)"},children:[e("button",{type:"button",onClick:u,"aria-label":n("doctorBanner.dismiss"),style:{position:"absolute",top:6,right:8,background:"transparent",border:"none",color:"var(--text-3)",fontSize:18,lineHeight:1,cursor:"pointer",padding:4},children:"×"}),e("div",{style:{fontSize:13,fontWeight:600,color:d,marginBottom:6},children:n(c?"doctorBanner.failTitle":"doctorBanner.warnTitleSoft")}),e("ul",{style:{margin:"6px 0 10px",paddingLeft:18,fontSize:12,lineHeight:1.5,color:"var(--text-2)"},children:[r.slice(0,3).map(b=>{const y=ea(b);return e("li",{children:[e("strong",{children:[ta(b),":"]})," ",Zr(b),y&&e(Z,{children:[" — ",e("em",{style:{color:"var(--text-3)"},children:y})]})]},b.id)}),r.length>3&&e("li",{style:{color:"var(--text-3)"},children:n("doctorBanner.moreCount",{n:r.length-3})})]}),c&&e("div",{style:{display:"flex",gap:8,alignItems:"center",flexWrap:"wrap"},children:[e("button",{type:"button",class:"btn",onClick:m,style:{fontSize:12,padding:"4px 12px"},children:n("doctorBanner.getHelp")}),e("span",{style:{fontSize:11,color:"var(--text-3)"},children:n("doctorBanner.helpHint")})]})]})}const ho="memesh.insightsBanner.dismissed";function ra({currentTab:t,onNavigateToInsights:s}){const[o,i]=M(0),[r,a]=M(()=>{try{return sessionStorage.getItem(ho)==="true"}catch{return!1}});if(ee(()=>{let c=!0;const d=()=>{Y("GET","/v1/dream/proposals?status=pending").then(l=>{if(!c)return;const b=Array.isArray(l)?l:l?.data??[];i(b.length)}).catch(()=>{})};d();const g=()=>d();return window.addEventListener("memesh:data-changed",g),()=>{c=!1,window.removeEventListener("memesh:data-changed",g)}},[]),t==="Home"||r||o===0)return null;function u(c){c.stopPropagation(),a(!0);try{sessionStorage.setItem(ho,"true")}catch{}}const m=n("banner.pendingInsights",{n:o,s:o===1?"":"s"});return e("div",{role:"button","aria-label":n("banner.viewAll"),onClick:s,onKeyDown:c=>{(c.key==="Enter"||c.key===" ")&&(c.preventDefault(),s())},tabIndex:0,style:{position:"relative",margin:"12px auto 0",maxWidth:920,padding:"10px 40px 10px 16px",border:"1px solid rgba(143, 242, 92, 0.32)",borderRadius:"var(--radius)",background:"var(--life-soft)",color:"var(--text-1)",cursor:"pointer",fontSize:13,lineHeight:1.5,display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},children:[e("span",{style:{flex:1},children:[e("span",{style:{marginRight:6},"aria-hidden":"true",children:"💡"}),m]}),e("button",{type:"button",onClick:u,"aria-label":n("banner.dismiss"),style:{position:"absolute",top:6,right:8,background:"transparent",border:"none",color:"var(--text-3)",fontSize:18,lineHeight:1,cursor:"pointer",padding:4},children:"×"})]})}const Oo=["Home","Memories","Project","Graph","Settings"],aa={Home:"tab.home",Memories:"tab.memories",Project:"tab.project",Graph:"tab.graph",Settings:"tab.settings"},ia={Insights:"Home",Analytics:"Home",Search:"Memories",Browse:"Memories",Manage:"Memories",Lessons:"Memories"},Bo="memesh.tab";function la(){const t=s=>s?Oo.includes(s)?s:ia[s]??null:null;try{const s=new URLSearchParams(window.location.search),o=t(s.get("tab"));if(o)return o;const i=t(localStorage.getItem(Bo));if(i)return i}catch{}return"Home"}function ca(){const[t,s]=M(()=>mn()),[o,i]=M(la),[r,a]=M(()=>new Set);ee(()=>{a(f=>f.has(o)?f:new Set(f).add(o))},[o]);const u=f=>r.has(f)||o===f;ee(()=>{try{localStorage.setItem(Bo,o)}catch{}},[o]);const[m,c]=M(null),[d,g]=M(""),[l,b]=M(!1);ee(()=>{const f=()=>b(!0);return window.addEventListener("memesh:auth-required",f),()=>window.removeEventListener("memesh:auth-required",f)},[]);const[y,h]=M(!1),v=Ce(()=>{Y("GET","/v1/health").then(f=>{c(f),g(""),b(!1)}).catch(f=>{if(f instanceof fs){h(is()!==null),b(!0),g("");return}g(f.message)})},[]);ee(()=>{v();const f=()=>v();return window.addEventListener("memesh:data-changed",f),()=>window.removeEventListener("memesh:data-changed",f)},[v]);const w=Oo.map(f=>({key:f,label:n(aa[f])}));return l?e(Jr,{currentToken:is(),rejected:y,onSubmit:f=>{vn(f),h(!1),b(!1),v()}}):e("div",{class:"shell",children:[e(pn,{health:m,error:d}),e(na,{}),e(ra,{currentTab:o,onNavigateToInsights:()=>i("Home")}),e(Qr,{health:m}),e(fn,{tabs:w,active:o,onSelect:f=>i(f)}),e("div",{class:"main",children:[e("div",{id:"panel-Home",role:"tabpanel","aria-labelledby":"tab-Home",class:`panel ${o==="Home"?"active":""}`,children:o==="Home"&&e(er,{})}),e("div",{id:"panel-Memories",role:"tabpanel","aria-labelledby":"tab-Memories",class:`panel ${o==="Memories"?"active":""}`,children:u("Memories")&&e(vr,{health:m})}),e("div",{id:"panel-Project",role:"tabpanel","aria-labelledby":"tab-Project",class:`panel ${o==="Project"?"active":""}`,children:u("Project")&&e(Lr,{health:m})}),e("div",{id:"panel-Graph",role:"tabpanel","aria-labelledby":"tab-Graph",class:`panel ${o==="Graph"?"active":""}`,children:o==="Graph"&&e(Gr,{})}),e("div",{id:"panel-Settings",role:"tabpanel","aria-labelledby":"tab-Settings",class:`panel ${o==="Settings"?"active":""}`,children:o==="Settings"&&e(Dr,{locale:t,onLocaleChange:s})})]}),e(Xr,{health:m})]})}on(e(ca,{}),document.getElementById("app"));
-
+`)}`,v=`https://github.com/PCIRCLE-AI/memesh/issues/new?title=${encodeURIComponent("[Bug] memesh doctor reported issues")}&body=${encodeURIComponent(y)}&labels=${encodeURIComponent("feedback,from-dashboard,bug,doctor-warning")}`;window.open(v,"_blank")}const c=t.status==="FAIL",d=c?"var(--danger)":"var(--warning)",g=c?"var(--danger-soft)":"var(--warning-soft)";return e("div",{role:"alert",style:{position:"relative",margin:"12px auto 0",maxWidth:920,padding:"12px 16px",border:`1px solid ${d}`,borderRadius:"var(--radius)",background:g,color:"var(--text-1)"},children:[e("button",{type:"button",onClick:u,"aria-label":n("doctorBanner.dismiss"),style:{position:"absolute",top:6,right:8,background:"transparent",border:"none",color:"var(--text-3)",fontSize:18,lineHeight:1,cursor:"pointer",padding:4},children:"×"}),e("div",{style:{fontSize:13,fontWeight:600,color:d,marginBottom:6},children:n(c?"doctorBanner.failTitle":"doctorBanner.warnTitleSoft")}),e("ul",{style:{margin:"6px 0 10px",paddingLeft:18,fontSize:12,lineHeight:1.5,color:"var(--text-2)"},children:[r.slice(0,3).map(b=>{const y=ea(b);return e("li",{children:[e("strong",{children:[ta(b),":"]})," ",Zr(b),y&&e(Z,{children:[" — ",e("em",{style:{color:"var(--text-3)"},children:y})]})]},b.id)}),r.length>3&&e("li",{style:{color:"var(--text-3)"},children:n("doctorBanner.moreCount",{n:r.length-3})})]}),c&&e("div",{style:{display:"flex",gap:8,alignItems:"center",flexWrap:"wrap"},children:[e("button",{type:"button",class:"btn",onClick:m,style:{fontSize:12,padding:"4px 12px"},children:n("doctorBanner.getHelp")}),e("span",{style:{fontSize:11,color:"var(--text-3)"},children:n("doctorBanner.helpHint")})]})]})}const ho="memesh.insightsBanner.dismissed";function ra({currentTab:t,onNavigateToInsights:s}){const[o,i]=M(0),[r,a]=M(()=>{try{return sessionStorage.getItem(ho)==="true"}catch{return!1}});if(ee(()=>{let c=!0;const d=()=>{Y("GET","/v1/dream/proposals?status=pending").then(l=>{if(!c)return;const b=Array.isArray(l)?l:l?.data??[];i(b.length)}).catch(()=>{})};d();const g=()=>d();return window.addEventListener("memesh:data-changed",g),()=>{c=!1,window.removeEventListener("memesh:data-changed",g)}},[]),t==="Home"||r||o===0)return null;function u(c){c.stopPropagation(),a(!0);try{sessionStorage.setItem(ho,"true")}catch{}}const m=n("banner.pendingInsights",{n:o,s:o===1?"":"s"});return e("div",{role:"button","aria-label":n("banner.viewAll"),onClick:s,onKeyDown:c=>{(c.key==="Enter"||c.key===" ")&&(c.preventDefault(),s())},tabIndex:0,style:{position:"relative",margin:"12px auto 0",maxWidth:920,padding:"10px 40px 10px 16px",border:"1px solid rgba(143, 242, 92, 0.32)",borderRadius:"var(--radius)",background:"var(--life-soft)",color:"var(--text-1)",cursor:"pointer",fontSize:13,lineHeight:1.5,display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},children:[e("span",{style:{flex:1},children:[e("span",{style:{marginRight:6},"aria-hidden":"true",children:"💡"}),m]}),e("button",{type:"button",onClick:u,"aria-label":n("banner.dismiss"),style:{position:"absolute",top:6,right:8,background:"transparent",border:"none",color:"var(--text-3)",fontSize:18,lineHeight:1,cursor:"pointer",padding:4},children:"×"})]})}const Oo=["Home","Memories","Project","Graph","Settings"],aa={Home:"tab.home",Memories:"tab.memories",Project:"tab.project",Graph:"tab.graph",Settings:"tab.settings"},ia={Insights:"Home",Analytics:"Home",Search:"Memories",Browse:"Memories",Manage:"Memories",Lessons:"Memories"},Bo="memesh.tab";function la(){const t=s=>s?Oo.includes(s)?s:ia[s]??null:null;try{const s=new URLSearchParams(window.location.search),o=t(s.get("tab"));if(o)return o;const i=t(localStorage.getItem(Bo));if(i)return i}catch{}return"Home"}function ca(){const[t,s]=M(()=>mn()),[o,i]=M(la),[r,a]=M(()=>new Set);ee(()=>{a(f=>f.has(o)?f:new Set(f).add(o))},[o]);const u=f=>r.has(f)||o===f;ee(()=>{try{localStorage.setItem(Bo,o)}catch{}},[o]);const[m,c]=M(null),[d,g]=M(""),[l,b]=M(!1);ee(()=>{const f=()=>b(!0);return window.addEventListener("memesh:auth-required",f),()=>window.removeEventListener("memesh:auth-required",f)},[]);const[y,h]=M(!1),v=Ce(()=>{Y("GET","/v1/health").then(f=>{c(f),g(""),b(!1)}).catch(f=>{if(f instanceof fs){h(is()!==null),b(!0),g("");return}g(f.message)})},[]);ee(()=>{v();const f=()=>v();return window.addEventListener("memesh:data-changed",f),()=>window.removeEventListener("memesh:data-changed",f)},[v]);const w=Oo.map(f=>({key:f,label:n(aa[f])}));return l?e(Jr,{currentToken:is(),rejected:y,onSubmit:f=>{vn(f),h(!1),b(!1),v()}}):e("div",{class:"shell",children:[e(pn,{health:m,error:d}),e("div",{class:"notice-slot",children:[e(na,{}),e(Qr,{health:m}),e(ra,{currentTab:o,onNavigateToInsights:()=>i("Home")})]}),e(fn,{tabs:w,active:o,onSelect:f=>i(f)}),e("div",{class:"main",children:[e("div",{id:"panel-Home",role:"tabpanel","aria-labelledby":"tab-Home",class:`panel ${o==="Home"?"active":""}`,children:o==="Home"&&e(er,{})}),e("div",{id:"panel-Memories",role:"tabpanel","aria-labelledby":"tab-Memories",class:`panel ${o==="Memories"?"active":""}`,children:u("Memories")&&e(vr,{health:m})}),e("div",{id:"panel-Project",role:"tabpanel","aria-labelledby":"tab-Project",class:`panel ${o==="Project"?"active":""}`,children:u("Project")&&e(Lr,{health:m})}),e("div",{id:"panel-Graph",role:"tabpanel","aria-labelledby":"tab-Graph",class:`panel ${o==="Graph"?"active":""}`,children:o==="Graph"&&e(Gr,{})}),e("div",{id:"panel-Settings",role:"tabpanel","aria-labelledby":"tab-Settings",class:`panel ${o==="Settings"?"active":""}`,children:o==="Settings"&&e(Dr,{locale:t,onLocaleChange:s})})]}),e(Xr,{health:m})]})}on(e(ca,{}),document.getElementById("app"));
+