From 085c1ed5b8ffe2c05a5b032c0930917ea5f82096 Mon Sep 17 00:00:00 2001 From: Alan Shurafa Date: Sat, 4 Jul 2026 00:19:52 -0400 Subject: [PATCH 1/4] [dashboards] Add wiki API client layer and login probe The wiki surface needs a typed client and feature-detection before any UI can render. Mirror the CRM pattern so the two optional surfaces stay symmetric and a brain without the wiki schema degrades the same way. - lib/types.ts: WikiPage/WikiSection and response types with keys matching the /wiki REST contract literally, so the UI can't drift from the gateway. - lib/api.ts: wikiAvailable() probes GET /wiki/pages (never throws, like crmAvailable) plus typed wrappers for every /wiki route. Page slugs are arbitrary user text, so they are percent-encoded into every path segment. - lib/auth.ts: cache wikiEnabled on the session; tolerate undefined on cookies minted before the field existed. - app/login: probe the wiki surface once beside the crm probe, never fatal. - Add react-markdown (renderer arrives in a later commit). --- .../app/login/page.tsx | 7 +- .../open-brain-dashboard-pro/lib/api.ts | 104 ++ .../open-brain-dashboard-pro/lib/auth.ts | 6 + .../open-brain-dashboard-pro/lib/types.ts | 92 ++ .../package-lock.json | 1314 ++++++++++++++++- .../open-brain-dashboard-pro/package.json | 1 + 6 files changed, 1451 insertions(+), 73 deletions(-) diff --git a/dashboards/open-brain-dashboard-pro/app/login/page.tsx b/dashboards/open-brain-dashboard-pro/app/login/page.tsx index a0eaef36..99a4dd2f 100644 --- a/dashboards/open-brain-dashboard-pro/app/login/page.tsx +++ b/dashboards/open-brain-dashboard-pro/app/login/page.tsx @@ -1,6 +1,6 @@ import { redirect } from "next/navigation"; import { getSession } from "@/lib/auth"; -import { checkHealth, crmAvailable, ApiError } from "@/lib/api"; +import { checkHealth, crmAvailable, wikiAvailable, ApiError } from "@/lib/api"; import { LoginForm } from "./LoginForm"; async function loginAction(formData: FormData) { @@ -29,10 +29,15 @@ async function loginAction(formData: FormData) { // Never fatal: a brain without the crm-core schema simply gets crmEnabled=false. const crmEnabled = await crmAvailable(key); + // Same for the optional wiki surface. Never fatal: a brain without the + // wiki-pages schema returns 404 on /wiki/pages, so this comes back false. + const wikiEnabled = await wikiAvailable(key); + const session = await getSession(); session.apiKey = key; session.loggedIn = true; session.crmEnabled = crmEnabled; + session.wikiEnabled = wikiEnabled; await session.save(); redirect("/"); diff --git a/dashboards/open-brain-dashboard-pro/lib/api.ts b/dashboards/open-brain-dashboard-pro/lib/api.ts index bff68bc7..12c0bb9d 100644 --- a/dashboards/open-brain-dashboard-pro/lib/api.ts +++ b/dashboards/open-brain-dashboard-pro/lib/api.ts @@ -18,6 +18,15 @@ import type { CrmNote, CrmTask, CrmImportantDate, + WikiPageKind, + WikiPageListResponse, + WikiPageDetailResponse, + WikiCreatePageResponse, + WikiWriteSectionResponse, + WikiAcceptPendingResponse, + WikiRejectPendingResponse, + WikiLockResponse, + WikiArchivePageResponse, } from "./types"; const API_URL = process.env.NEXT_PUBLIC_API_URL; @@ -532,3 +541,98 @@ export async function fetchCrmTimeline( const qs = sp.toString(); return apiFetch(apiKey, `/crm/contacts/${id}/timeline${qs ? `?${qs}` : ""}`); } + +// ─── Wiki (optional) ───────────────────────────────────────────────────────── +// These hit the /wiki/* gateway routes, present only when the wiki-pages schema +// is installed. `wikiAvailable` probes once so the UI can hide the section (or +// show the inline "schema not installed" empty-state) entirely. A brain WITHOUT +// the schema returns 404 on GET /wiki/pages, so the probe simply comes back +// false. Section identifiers are UUIDs; a page slug is arbitrary user text and +// is always percent-encoded before it goes into a path segment. + +/** Feature detection: true when the brain exposes the wiki surface. */ +export async function wikiAvailable(apiKey: string): Promise { + try { + await apiFetch(apiKey, "/wiki/pages?per_page=1"); + return true; + } catch { + return false; + } +} + +export async function fetchWikiPages( + apiKey: string, + params?: { page_kind?: string; page?: number; per_page?: number } +): Promise { + const sp = new URLSearchParams(); + if (params?.page_kind) sp.set("page_kind", params.page_kind); + // IN-07: preserve explicit numeric values (page/per_page are always >= 1 here, + // but keep the guard consistent with the rest of this module). + if (params?.page !== undefined) sp.set("page", String(params.page)); + if (params?.per_page !== undefined) sp.set("per_page", String(params.per_page)); + const qs = sp.toString(); + return apiFetch(apiKey, `/wiki/pages${qs ? `?${qs}` : ""}`); +} + +export async function createWikiPage( + apiKey: string, + data: { slug: string; title: string; page_kind?: WikiPageKind; metadata?: Record } +): Promise { + return apiFetch(apiKey, "/wiki/pages", { method: "POST", body: JSON.stringify(data) }); +} + +export async function fetchWikiPage( + apiKey: string, + slug: string +): Promise { + return apiFetch(apiKey, `/wiki/pages/${encodeURIComponent(slug)}`); +} + +export async function writeWikiSection( + apiKey: string, + slug: string, + sectionKey: string, + data: { body_md: string; heading?: string; display_order?: number } +): Promise { + return apiFetch( + apiKey, + `/wiki/pages/${encodeURIComponent(slug)}/sections/${encodeURIComponent(sectionKey)}`, + { method: "PUT", body: JSON.stringify(data) } + ); +} + +export async function acceptWikiPending( + apiKey: string, + sectionId: string +): Promise { + return apiFetch(apiKey, `/wiki/sections/${encodeURIComponent(sectionId)}/accept-pending`, { + method: "POST", + }); +} + +export async function rejectWikiPending( + apiKey: string, + sectionId: string +): Promise { + return apiFetch(apiKey, `/wiki/sections/${encodeURIComponent(sectionId)}/reject-pending`, { + method: "POST", + }); +} + +export async function setWikiSectionLock( + apiKey: string, + sectionId: string, + locked: boolean +): Promise { + return apiFetch(apiKey, `/wiki/sections/${encodeURIComponent(sectionId)}/lock`, { + method: "POST", + body: JSON.stringify({ locked }), + }); +} + +export async function archiveWikiPage( + apiKey: string, + slug: string +): Promise { + return apiFetch(apiKey, `/wiki/pages/${encodeURIComponent(slug)}`, { method: "DELETE" }); +} diff --git a/dashboards/open-brain-dashboard-pro/lib/auth.ts b/dashboards/open-brain-dashboard-pro/lib/auth.ts index cd2af8f9..6f7f687f 100644 --- a/dashboards/open-brain-dashboard-pro/lib/auth.ts +++ b/dashboards/open-brain-dashboard-pro/lib/auth.ts @@ -8,6 +8,12 @@ export interface SessionData { restrictedUnlocked?: boolean; /** Cached at login: does this brain expose the optional /crm surface? */ crmEnabled?: boolean; + /** + * Cached at login: does this brain expose the optional /wiki surface? + * Undefined on cookies minted before this field existed — treat as "unknown" + * (never assume true) everywhere it's read. + */ + wikiEnabled?: boolean; } export class AuthError extends Error { diff --git a/dashboards/open-brain-dashboard-pro/lib/types.ts b/dashboards/open-brain-dashboard-pro/lib/types.ts index 860b53ff..8409e166 100644 --- a/dashboards/open-brain-dashboard-pro/lib/types.ts +++ b/dashboards/open-brain-dashboard-pro/lib/types.ts @@ -323,3 +323,95 @@ export interface AddToBrainResult { extracted_count?: number | null; message: string; } + +// ─── Wiki (optional) ───────────────────────────────────────────────────────── +// Present only on brains with the wiki-pages schema and the /wiki gateway routes. +// Every id is a UUID string. Keys match the REST contract literally. + +export type WikiPageKind = "topic" | "entity" | "autobiography" | "custom"; +export type WikiPageStatus = "active" | "archived"; +export type WikiSectionOrigin = "manual" | "generated"; + +/** A page as it appears in the list response (no sections). */ +export interface WikiPageSummary { + id: string; + slug: string; + title: string; + page_kind: WikiPageKind; + status: WikiPageStatus; + metadata: Record; + created_at: string; + updated_at: string; + section_count: number; +} + +/** A page as it appears on the detail response (no section_count there). */ +export interface WikiPage { + id: string; + slug: string; + title: string; + page_kind: WikiPageKind; + status: WikiPageStatus; + metadata: Record; + created_at: string; + updated_at: string; +} + +export interface WikiSection { + id: string; + section_key: string; + heading: string | null; + display_order: number; + origin: WikiSectionOrigin; + locked: boolean; + body_md: string; + pending_generated_md: string | null; + pending_generated_at: string | null; + generation_source: Record; + evidence_thought_ids: string[]; + created_at: string; + updated_at: string; +} + +export interface WikiPageListResponse { + data: WikiPageSummary[]; + total: number; + page: number; + per_page: number; +} + +export interface WikiPageDetailResponse { + page: WikiPage; + sections: WikiSection[]; +} + +export interface WikiCreatePageResponse { + page_id: string; + created: boolean; +} + +/** PUT a section returns which write path the regen guard took. */ +export interface WikiWriteSectionResponse { + section_id: string; + action: "created" | "updated" | "pending"; +} + +export interface WikiAcceptPendingResponse { + section_id: string; + action: "accepted" | "no_pending"; +} + +export interface WikiRejectPendingResponse { + section_id: string; + action: "rejected" | "no_pending"; +} + +export interface WikiLockResponse { + section_id: string; + locked: boolean; +} + +export interface WikiArchivePageResponse { + slug: string; + status: "archived"; +} diff --git a/dashboards/open-brain-dashboard-pro/package-lock.json b/dashboards/open-brain-dashboard-pro/package-lock.json index 12545982..609e7147 100644 --- a/dashboards/open-brain-dashboard-pro/package-lock.json +++ b/dashboards/open-brain-dashboard-pro/package-lock.json @@ -12,6 +12,7 @@ "next": "16.2.6", "react": "19.2.4", "react-dom": "19.2.4", + "react-markdown": "^10.1.0", "server-only": "^0.0.1" }, "devDependencies": { @@ -1546,13 +1547,39 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1567,6 +1594,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.19.41", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", @@ -1581,7 +1623,6 @@ "version": "19.2.15", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -1597,6 +1638,12 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.59.4", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.4.tgz", @@ -1892,6 +1939,12 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz", + "integrity": "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==", + "license": "ISC" + }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", @@ -2491,6 +2544,16 @@ "node": ">= 0.4" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2648,6 +2711,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2665,6 +2738,46 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -2691,6 +2804,16 @@ "dev": true, "license": "MIT" }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2733,7 +2856,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -2801,7 +2923,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2815,6 +2936,19 @@ } } }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2858,6 +2992,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -2868,6 +3011,19 @@ "node": ">=8" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -3520,6 +3676,16 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3530,6 +3696,12 @@ "node": ">=0.10.0" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3959,6 +4131,46 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -3976,6 +4188,16 @@ "hermes-estree": "0.25.1" } }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4013,6 +4235,12 @@ "node": ">=0.8.19" } }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -4052,6 +4280,30 @@ "url": "https://github.com/sponsors/brc-dd" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -4210,6 +4462,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -4269,6 +4531,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -4322,6 +4594,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -4920,6 +5204,16 @@ "dev": true, "license": "MIT" }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -4963,99 +5257,693 @@ "node": ">= 0.4" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "license": "MIT", - "engines": { - "node": ">= 8" + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", "license": "MIT", "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, - "engines": { - "node": ">=8.6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" }, - "engines": { - "node": "*" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/napi-postinstall": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", - "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", - "dev": true, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" }, "funding": { - "url": "https://opencollective.com/napi-postinstall" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, "license": "MIT" }, "node_modules/next": { @@ -5372,6 +6260,31 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5479,6 +6392,16 @@ "react-is": "^16.13.1" } }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5538,6 +6461,33 @@ "dev": true, "license": "MIT" }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -5582,6 +6532,39 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/resolve": { "version": "2.0.0-next.7", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", @@ -5953,6 +6936,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -6087,6 +7080,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -6110,6 +7117,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -6241,6 +7266,26 @@ "node": ">=8.0" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -6447,6 +7492,93 @@ "dev": true, "license": "MIT" }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unrs-resolver": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", @@ -6526,6 +7658,34 @@ "punycode": "^2.1.0" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6683,6 +7843,16 @@ "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/dashboards/open-brain-dashboard-pro/package.json b/dashboards/open-brain-dashboard-pro/package.json index 733dd174..99dad3fa 100644 --- a/dashboards/open-brain-dashboard-pro/package.json +++ b/dashboards/open-brain-dashboard-pro/package.json @@ -13,6 +13,7 @@ "next": "16.2.6", "react": "19.2.4", "react-dom": "19.2.4", + "react-markdown": "^10.1.0", "server-only": "^0.0.1" }, "devDependencies": { From 71f907f2baa8412c73d082c726a179a8518037f9 Mon Sep 17 00:00:00 2001 From: Alan Shurafa Date: Sat, 4 Jul 2026 00:20:15 -0400 Subject: [PATCH 2/4] [dashboards] Add wiki API route handlers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The browser must never hold the brain key, so every wiki mutation proxies through a server route that injects x-brain-key. Each handler runs requireSession() BEFORE parsing the body (house rule) so an unauthed request gets 401, not a 400 from validation, and never reaches body parse. One handler per mutation: page create (POST), section write (PUT), and per-section accept-pending / reject-pending / lock plus page archive (DELETE). Inputs are validated to the contract (allowed page_kind, boolean locked, integer display_order, non-empty required strings) so bad values fail fast here instead of as a 500 from a DB constraint. Upstream error bodies are logged server-side and never forwarded to the client — only a safe generic message and the passed-through status code. --- .../app/api/wiki/pages/[slug]/route.ts | 36 ++++++++++ .../[slug]/sections/[sectionKey]/route.ts | 65 +++++++++++++++++++ .../app/api/wiki/pages/route.ts | 59 +++++++++++++++++ .../sections/[id]/accept-pending/route.ts | 36 ++++++++++ .../app/api/wiki/sections/[id]/lock/route.ts | 43 ++++++++++++ .../sections/[id]/reject-pending/route.ts | 36 ++++++++++ 6 files changed, 275 insertions(+) create mode 100644 dashboards/open-brain-dashboard-pro/app/api/wiki/pages/[slug]/route.ts create mode 100644 dashboards/open-brain-dashboard-pro/app/api/wiki/pages/[slug]/sections/[sectionKey]/route.ts create mode 100644 dashboards/open-brain-dashboard-pro/app/api/wiki/pages/route.ts create mode 100644 dashboards/open-brain-dashboard-pro/app/api/wiki/sections/[id]/accept-pending/route.ts create mode 100644 dashboards/open-brain-dashboard-pro/app/api/wiki/sections/[id]/lock/route.ts create mode 100644 dashboards/open-brain-dashboard-pro/app/api/wiki/sections/[id]/reject-pending/route.ts diff --git a/dashboards/open-brain-dashboard-pro/app/api/wiki/pages/[slug]/route.ts b/dashboards/open-brain-dashboard-pro/app/api/wiki/pages/[slug]/route.ts new file mode 100644 index 00000000..7e32d1bf --- /dev/null +++ b/dashboards/open-brain-dashboard-pro/app/api/wiki/pages/[slug]/route.ts @@ -0,0 +1,36 @@ +import { NextRequest, NextResponse } from "next/server"; +import { archiveWikiPage, ApiError } from "@/lib/api"; +import { requireSession, AuthError } from "@/lib/auth"; + +// DELETE /api/wiki/pages/:slug — archive a page (soft delete). +// Proxies to DELETE /wiki/pages/:slug, which sets status='archived'. +export async function DELETE( + _request: NextRequest, + { params }: { params: Promise<{ slug: string }> } +) { + let apiKey: string; + try { + ({ apiKey } = await requireSession()); + } catch (err) { + if (err instanceof AuthError) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + throw err; + } + + const { slug } = await params; + if (!slug) { + return NextResponse.json({ error: "Missing slug" }, { status: 400 }); + } + + try { + const result = await archiveWikiPage(apiKey, slug); + return NextResponse.json(result); + } catch (err) { + if (err instanceof ApiError) { + console.error("[wiki/page:delete] upstream", err.status, err.upstreamBody); + return NextResponse.json({ error: "Upstream error" }, { status: err.status }); + } + console.error("[wiki/page:delete]", err); + return NextResponse.json({ error: "Failed to archive page" }, { status: 500 }); + } +} diff --git a/dashboards/open-brain-dashboard-pro/app/api/wiki/pages/[slug]/sections/[sectionKey]/route.ts b/dashboards/open-brain-dashboard-pro/app/api/wiki/pages/[slug]/sections/[sectionKey]/route.ts new file mode 100644 index 00000000..be21fb03 --- /dev/null +++ b/dashboards/open-brain-dashboard-pro/app/api/wiki/pages/[slug]/sections/[sectionKey]/route.ts @@ -0,0 +1,65 @@ +import { NextRequest, NextResponse } from "next/server"; +import { writeWikiSection, ApiError } from "@/lib/api"; +import { requireSession, AuthError } from "@/lib/auth"; + +// PUT /api/wiki/pages/:slug/sections/:sectionKey — create or edit a section. +// Proxies to PUT /wiki/pages/:slug/sections/:sectionKey. REST edits are +// manual-origin: the first human edit transfers ownership of the section. +export async function PUT( + request: NextRequest, + { params }: { params: Promise<{ slug: string; sectionKey: string }> } +) { + // Auth BEFORE body parse — unauthed requests get 401, not 400 (house rule). + let apiKey: string; + try { + ({ apiKey } = await requireSession()); + } catch (err) { + if (err instanceof AuthError) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + throw err; + } + + const { slug, sectionKey } = await params; + if (!slug || !sectionKey) { + return NextResponse.json({ error: "Missing slug or section key" }, { status: 400 }); + } + + try { + const body = (await request.json()) as { + body_md?: unknown; + heading?: unknown; + display_order?: unknown; + }; + + // body_md is required but may legitimately be an empty string (clears the + // section). Only reject when it is missing or not a string. + if (typeof body.body_md !== "string") { + return NextResponse.json({ error: "body_md is required" }, { status: 400 }); + } + const heading = + typeof body.heading === "string" ? body.heading : undefined; + + let displayOrder: number | undefined; + if (body.display_order !== undefined && body.display_order !== null) { + const n = Number(body.display_order); + if (!Number.isInteger(n)) { + return NextResponse.json({ error: "display_order must be an integer" }, { status: 400 }); + } + displayOrder = n; + } + + const result = await writeWikiSection(apiKey, slug, sectionKey, { + body_md: body.body_md, + heading, + display_order: displayOrder, + }); + return NextResponse.json(result); + } catch (err) { + if (err instanceof ApiError) { + console.error("[wiki/section:put] upstream", err.status, err.upstreamBody); + return NextResponse.json({ error: "Upstream error" }, { status: err.status }); + } + console.error("[wiki/section:put]", err); + return NextResponse.json({ error: "Failed to save section" }, { status: 500 }); + } +} diff --git a/dashboards/open-brain-dashboard-pro/app/api/wiki/pages/route.ts b/dashboards/open-brain-dashboard-pro/app/api/wiki/pages/route.ts new file mode 100644 index 00000000..fe27b224 --- /dev/null +++ b/dashboards/open-brain-dashboard-pro/app/api/wiki/pages/route.ts @@ -0,0 +1,59 @@ +import { NextRequest, NextResponse } from "next/server"; +import { createWikiPage, ApiError } from "@/lib/api"; +import { requireSession, AuthError } from "@/lib/auth"; +import type { WikiPageKind } from "@/lib/types"; + +const PAGE_KINDS: WikiPageKind[] = ["topic", "entity", "autobiography", "custom"]; + +// POST /api/wiki/pages — create a wiki page. Proxies to POST /wiki/pages. +export async function POST(request: NextRequest) { + // Auth BEFORE body parse — unauthed requests get 401, not 400 (house rule). + let apiKey: string; + try { + ({ apiKey } = await requireSession()); + } catch (err) { + if (err instanceof AuthError) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + throw err; + } + + try { + const body = (await request.json()) as { + slug?: unknown; + title?: unknown; + page_kind?: unknown; + metadata?: unknown; + }; + + const slug = typeof body.slug === "string" ? body.slug.trim() : ""; + const title = typeof body.title === "string" ? body.title.trim() : ""; + if (!slug) return NextResponse.json({ error: "slug is required" }, { status: 400 }); + if (!title) return NextResponse.json({ error: "title is required" }, { status: 400 }); + + // page_kind is optional; when present it must be one of the allowed kinds so a + // bad value never reaches the DB CHECK constraint as a 500. + let pageKind: WikiPageKind | undefined; + if (body.page_kind !== undefined && body.page_kind !== "") { + if (typeof body.page_kind !== "string" || !PAGE_KINDS.includes(body.page_kind as WikiPageKind)) { + return NextResponse.json({ error: "Invalid page_kind" }, { status: 400 }); + } + pageKind = body.page_kind as WikiPageKind; + } + + const metadata = + body.metadata && typeof body.metadata === "object" && !Array.isArray(body.metadata) + ? (body.metadata as Record) + : undefined; + + const result = await createWikiPage(apiKey, { slug, title, page_kind: pageKind, metadata }); + return NextResponse.json(result); + } catch (err) { + if (err instanceof ApiError) { + // Log the full upstream body server-side; never render it to the client. + console.error("[wiki/pages] upstream", err.status, err.upstreamBody); + return NextResponse.json({ error: "Upstream error" }, { status: err.status }); + } + console.error("[wiki/pages]", err); + return NextResponse.json({ error: "Failed to create page" }, { status: 500 }); + } +} diff --git a/dashboards/open-brain-dashboard-pro/app/api/wiki/sections/[id]/accept-pending/route.ts b/dashboards/open-brain-dashboard-pro/app/api/wiki/sections/[id]/accept-pending/route.ts new file mode 100644 index 00000000..2ac66708 --- /dev/null +++ b/dashboards/open-brain-dashboard-pro/app/api/wiki/sections/[id]/accept-pending/route.ts @@ -0,0 +1,36 @@ +import { NextRequest, NextResponse } from "next/server"; +import { acceptWikiPending, ApiError } from "@/lib/api"; +import { requireSession, AuthError } from "@/lib/auth"; + +// POST /api/wiki/sections/:id/accept-pending — promote a parked machine draft to +// the live body. Proxies to POST /wiki/sections/:id/accept-pending. +export async function POST( + _request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + let apiKey: string; + try { + ({ apiKey } = await requireSession()); + } catch (err) { + if (err instanceof AuthError) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + throw err; + } + + const { id } = await params; + if (!id) { + return NextResponse.json({ error: "Missing section id" }, { status: 400 }); + } + + try { + const result = await acceptWikiPending(apiKey, id); + return NextResponse.json(result); + } catch (err) { + if (err instanceof ApiError) { + console.error("[wiki/section:accept] upstream", err.status, err.upstreamBody); + return NextResponse.json({ error: "Upstream error" }, { status: err.status }); + } + console.error("[wiki/section:accept]", err); + return NextResponse.json({ error: "Failed to accept draft" }, { status: 500 }); + } +} diff --git a/dashboards/open-brain-dashboard-pro/app/api/wiki/sections/[id]/lock/route.ts b/dashboards/open-brain-dashboard-pro/app/api/wiki/sections/[id]/lock/route.ts new file mode 100644 index 00000000..9db5d20c --- /dev/null +++ b/dashboards/open-brain-dashboard-pro/app/api/wiki/sections/[id]/lock/route.ts @@ -0,0 +1,43 @@ +import { NextRequest, NextResponse } from "next/server"; +import { setWikiSectionLock, ApiError } from "@/lib/api"; +import { requireSession, AuthError } from "@/lib/auth"; + +// POST /api/wiki/sections/:id/lock — lock or unlock a section. A locked section +// only ever receives pending drafts from machine writers. Proxies to +// POST /wiki/sections/:id/lock with { locked }. +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + // Auth BEFORE body parse — unauthed requests get 401, not 400 (house rule). + let apiKey: string; + try { + ({ apiKey } = await requireSession()); + } catch (err) { + if (err instanceof AuthError) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + throw err; + } + + const { id } = await params; + if (!id) { + return NextResponse.json({ error: "Missing section id" }, { status: 400 }); + } + + try { + const body = (await request.json()) as { locked?: unknown }; + if (typeof body.locked !== "boolean") { + return NextResponse.json({ error: "locked must be a boolean" }, { status: 400 }); + } + + const result = await setWikiSectionLock(apiKey, id, body.locked); + return NextResponse.json(result); + } catch (err) { + if (err instanceof ApiError) { + console.error("[wiki/section:lock] upstream", err.status, err.upstreamBody); + return NextResponse.json({ error: "Upstream error" }, { status: err.status }); + } + console.error("[wiki/section:lock]", err); + return NextResponse.json({ error: "Failed to update lock" }, { status: 500 }); + } +} diff --git a/dashboards/open-brain-dashboard-pro/app/api/wiki/sections/[id]/reject-pending/route.ts b/dashboards/open-brain-dashboard-pro/app/api/wiki/sections/[id]/reject-pending/route.ts new file mode 100644 index 00000000..b273d63a --- /dev/null +++ b/dashboards/open-brain-dashboard-pro/app/api/wiki/sections/[id]/reject-pending/route.ts @@ -0,0 +1,36 @@ +import { NextRequest, NextResponse } from "next/server"; +import { rejectWikiPending, ApiError } from "@/lib/api"; +import { requireSession, AuthError } from "@/lib/auth"; + +// POST /api/wiki/sections/:id/reject-pending — discard a parked machine draft, +// leaving the live body untouched. Proxies to POST /wiki/sections/:id/reject-pending. +export async function POST( + _request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + let apiKey: string; + try { + ({ apiKey } = await requireSession()); + } catch (err) { + if (err instanceof AuthError) + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + throw err; + } + + const { id } = await params; + if (!id) { + return NextResponse.json({ error: "Missing section id" }, { status: 400 }); + } + + try { + const result = await rejectWikiPending(apiKey, id); + return NextResponse.json(result); + } catch (err) { + if (err instanceof ApiError) { + console.error("[wiki/section:reject] upstream", err.status, err.upstreamBody); + return NextResponse.json({ error: "Upstream error" }, { status: err.status }); + } + console.error("[wiki/section:reject]", err); + return NextResponse.json({ error: "Failed to reject draft" }, { status: 500 }); + } +} From 440a6c0292c1b6a5b009cdc28485e65ca9ddbfbe Mon Sep 17 00:00:00 2001 From: Alan Shurafa Date: Sat, 4 Jul 2026 00:21:20 -0400 Subject: [PATCH 3/4] [dashboards] Add wiki list and page UI with draft review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wiki's whole point is regeneration that never stomps human edits, so the UI has to make section ownership and the machine-vs-human handshake legible. - /wiki: page list with kind chips, section counts, pagination (the /contacts idiom), and a "new page" form. When the schema is absent — wikiEnabled===false or GET /wiki/pages 404s — it renders a one-paragraph inline notice instead of an error. A later PR swaps this for a shared setup-state component, so it is kept minimal and self-contained. - /wiki/[slug]: sections in display order, each a panel with an origin chip (yours / generated), lock toggle, evidence chip linking supporting thoughts, inline edit (which takes ownership), and archive with confirm. When a machine writer proposes an update to a human-owned section, an amber review panel above the body diffs current vs proposed with accept / reject — mirroring the CRM proposal accept/reject flow. - MarkdownBody: section bodies are agent-writable over MCP, so they are untrusted. Rendered with react-markdown and NO rehype-raw, so embedded HTML is shown as text and never executed — the XSS boundary. - README: document the optional wiki surface and its degraded empty-state. Co-Authored-By: Claude Fable 5 --- dashboards/open-brain-dashboard-pro/README.md | 14 + .../app/wiki/NewPageForm.tsx | 166 ++++++++++ .../app/wiki/[slug]/AddSectionForm.tsx | 141 ++++++++ .../app/wiki/[slug]/ArchivePageButton.tsx | 74 +++++ .../app/wiki/[slug]/WikiSectionPanel.tsx | 313 ++++++++++++++++++ .../app/wiki/[slug]/page.tsx | 122 +++++++ .../app/wiki/page.tsx | 208 ++++++++++++ .../components/MarkdownBody.tsx | 138 ++++++++ 8 files changed, 1176 insertions(+) create mode 100644 dashboards/open-brain-dashboard-pro/app/wiki/NewPageForm.tsx create mode 100644 dashboards/open-brain-dashboard-pro/app/wiki/[slug]/AddSectionForm.tsx create mode 100644 dashboards/open-brain-dashboard-pro/app/wiki/[slug]/ArchivePageButton.tsx create mode 100644 dashboards/open-brain-dashboard-pro/app/wiki/[slug]/WikiSectionPanel.tsx create mode 100644 dashboards/open-brain-dashboard-pro/app/wiki/[slug]/page.tsx create mode 100644 dashboards/open-brain-dashboard-pro/app/wiki/page.tsx create mode 100644 dashboards/open-brain-dashboard-pro/components/MarkdownBody.tsx diff --git a/dashboards/open-brain-dashboard-pro/README.md b/dashboards/open-brain-dashboard-pro/README.md index 53cfa476..9f3503a1 100644 --- a/dashboards/open-brain-dashboard-pro/README.md +++ b/dashboards/open-brain-dashboard-pro/README.md @@ -29,6 +29,19 @@ If your brain runs the CRM truth layer (the `crm-core` + `crm-engagement` schema The Contacts and Proposals nav entries, and the open-proposals badge on the sidebar, appear **only when the brain exposes the `/crm` surface**. The dashboard probes for it once at login and caches the result in the session, so a brain without the CRM layer never shows these entries and behaves exactly as before. Each CRM read also degrades on its own: if an individual route is missing or errors, that panel renders empty instead of blanking the page. +## Wiki (optional) + +If your brain runs the persistent-wiki layer (the `schemas/wiki-pages` schema and the `/wiki/*` routes on `open-brain-rest`), the dashboard grows a wiki surface: + +| Page | What you get | +|------|--------------| +| **Wiki** (`/wiki`) | Page list filtered by kind (topic / entity / autobiography / custom), with section counts and a "new page" form. | +| **Wiki page** (`/wiki/:slug`) | Sections in display order, each with its markdown body rendered sanitized, an origin chip (yours / generated), a lock toggle, and an evidence chip linking supporting thoughts. Edit a section inline (your edit takes ownership); add a section; archive the page. When a machine writer proposes an update to a section you own, a review panel shows the current body against the proposed draft with **accept / reject**. | + +Wiki pages are reachable at `/wiki` (a dedicated sidebar entry lands in a later change). The dashboard probes for the `/wiki` surface once at login and caches the result in the session. When the schema is absent — the probe comes back negative, or `GET /wiki/pages` returns 404 — the page renders a short inline notice ("Wiki schema not installed — apply `schemas/wiki-pages` to enable") instead of an error, so a brain without the wiki layer behaves exactly as before. + +Section bodies are agent-writable over MCP, so the dashboard treats them as untrusted: markdown is rendered with `react-markdown` and **no raw-HTML plugin**, so any embedded HTML is shown as text and never executed. + ## Screenshots Screenshots go in `docs/screenshots/` and should be referenced from this README once you add them. @@ -98,6 +111,7 @@ The dashboard calls these endpoints on your Open Brain REST gateway (all authent | `/duplicates`, `/duplicates/resolve` | GET / POST | Duplicates page | Optional — page shows an error otherwise | | `/ingest`, `/ingestion-jobs`, `/ingestion-jobs/:id`, `/ingestion-jobs/:id/execute` | POST / GET | Ingest page | Optional — page still loads without jobs | | `/crm/*` (contacts, proposals, notes, tasks, important-dates, timeline, history, …) | GET / POST / PATCH | Contacts, Proposals, contact detail panels | Optional — CRM surface is hidden unless `/crm` is detected at login | +| `/wiki/*` (pages, pages/:slug, sections/:id/accept-pending, reject-pending, lock, …) | GET / POST / PUT / DELETE | Wiki list, wiki page, section edit / lock / draft review | Optional — wiki surface degrades to an inline notice unless `/wiki` is detected at login | > **On `/reflections/*`:** The ExoCortex upstream dashboard staged a reflections feature. This fork does not yet ship a reflections UI surface, but the architecture is ready: if you add a reflection panel later and your gateway doesn't serve `/reflections/*`, expect a 404 that the UI should swallow. The existing optional endpoints already degrade this way — the Connections panel, Duplicates page, and Ingest history all swallow fetch errors and render an empty/neutral state instead of crashing. diff --git a/dashboards/open-brain-dashboard-pro/app/wiki/NewPageForm.tsx b/dashboards/open-brain-dashboard-pro/app/wiki/NewPageForm.tsx new file mode 100644 index 00000000..ca55ad9a --- /dev/null +++ b/dashboards/open-brain-dashboard-pro/app/wiki/NewPageForm.tsx @@ -0,0 +1,166 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import type { WikiPageKind } from "@/lib/types"; + +const PAGE_KINDS: WikiPageKind[] = ["topic", "entity", "autobiography", "custom"]; + +// Turn a title into a reasonable default slug: lowercase, spaces→hyphens, drop +// anything that isn't a word char or hyphen. The user can still override it. +function slugify(input: string): string { + return input + .toLowerCase() + .trim() + .replace(/[^\w\s-]/g, "") + .replace(/[\s_]+/g, "-") + .replace(/-+/g, "-") + .replace(/^-|-$/g, ""); +} + +export function NewPageForm() { + const router = useRouter(); + const [open, setOpen] = useState(false); + const [title, setTitle] = useState(""); + const [slug, setSlug] = useState(""); + const [slugEdited, setSlugEdited] = useState(false); + const [kind, setKind] = useState("topic"); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + + const effectiveSlug = slugEdited ? slug : slugify(title); + + function reset() { + setTitle(""); + setSlug(""); + setSlugEdited(false); + setKind("topic"); + setError(null); + } + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + if (submitting) return; + const finalSlug = (slugEdited ? slug : slugify(title)).trim(); + const finalTitle = title.trim(); + if (!finalTitle) { + setError("Enter a title."); + return; + } + if (!finalSlug) { + setError("Enter a slug."); + return; + } + + setSubmitting(true); + setError(null); + try { + const res = await fetch("/api/wiki/pages", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ slug: finalSlug, title: finalTitle, page_kind: kind }), + }); + if (!res.ok) { + const data = (await res.json().catch(() => ({}))) as { error?: string }; + throw new Error(data.error || "Failed to create page"); + } + reset(); + setOpen(false); + // The list re-reads server-side; then jump to the new page. + router.refresh(); + router.push(`/wiki/${encodeURIComponent(finalSlug)}`); + } catch (err) { + setError(err instanceof Error ? err.message : "Something went wrong"); + } finally { + setSubmitting(false); + } + } + + if (!open) { + return ( + + ); + } + + return ( +
+
+
+ + setTitle(e.target.value)} + placeholder="Vector search" + className="w-full px-3 py-1.5 bg-bg-elevated border border-border rounded text-text-primary text-sm placeholder-text-muted focus:outline-none focus:border-violet focus:ring-1 focus:ring-violet/30 transition" + /> +
+
+ + { + setSlug(e.target.value); + setSlugEdited(true); + }} + placeholder="vector-search" + className="w-full px-3 py-1.5 bg-bg-elevated border border-border rounded text-text-primary text-sm placeholder-text-muted focus:outline-none focus:border-violet focus:ring-1 focus:ring-violet/30 transition font-mono" + /> +
+
+ + +
+
+ + {error &&

{error}

} + +
+ + +
+
+ ); +} diff --git a/dashboards/open-brain-dashboard-pro/app/wiki/[slug]/AddSectionForm.tsx b/dashboards/open-brain-dashboard-pro/app/wiki/[slug]/AddSectionForm.tsx new file mode 100644 index 00000000..c6251c8c --- /dev/null +++ b/dashboards/open-brain-dashboard-pro/app/wiki/[slug]/AddSectionForm.tsx @@ -0,0 +1,141 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; + +// Adds a new section via the same PUT route the editor uses — the write guard +// treats a first write to a new section_key as a create. +export function AddSectionForm({ slug }: { slug: string }) { + const router = useRouter(); + const [open, setOpen] = useState(false); + const [sectionKey, setSectionKey] = useState(""); + const [heading, setHeading] = useState(""); + const [body, setBody] = useState(""); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + + function reset() { + setSectionKey(""); + setHeading(""); + setBody(""); + setError(null); + } + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + if (submitting) return; + const key = sectionKey.trim(); + if (!key) { + setError("Enter a section key."); + return; + } + + setSubmitting(true); + setError(null); + try { + const res = await fetch( + `/api/wiki/pages/${encodeURIComponent(slug)}/sections/${encodeURIComponent(key)}`, + { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + body_md: body, + heading: heading.trim() || undefined, + }), + } + ); + if (!res.ok) { + const data = (await res.json().catch(() => ({}))) as { error?: string }; + throw new Error(data.error || "Failed to add section"); + } + reset(); + setOpen(false); + router.refresh(); + } catch (err) { + setError(err instanceof Error ? err.message : "Something went wrong"); + } finally { + setSubmitting(false); + } + } + + if (!open) { + return ( + + ); + } + + return ( +
+
+
+ + setSectionKey(e.target.value)} + placeholder="overview" + className="w-full px-3 py-1.5 bg-bg-elevated border border-border rounded text-text-primary text-sm placeholder-text-muted focus:outline-none focus:border-violet focus:ring-1 focus:ring-violet/30 transition font-mono" + /> +
+
+ + setHeading(e.target.value)} + placeholder="Overview (optional)" + className="w-full px-3 py-1.5 bg-bg-elevated border border-border rounded text-text-primary text-sm placeholder-text-muted focus:outline-none focus:border-violet focus:ring-1 focus:ring-violet/30 transition" + /> +
+
+
+ +