Initial public push: docs cosmos v4 + AI module + framework groundwork

This is the snapshot the production landing site (nibiru-framework.com) is
deployed from. Brings together the recent splash + docs migration to the v4
"Cosmos" design system, the new in-framework AI module, and the framework
groundwork that backs the framework-reference extraction.

What lands:
- docs/: Astro + Starlight site with the v4 dark cosmic palette, GalaxyHero
  canvas constellation, Mission Control chat (wired to /api/oracle →
  api.neuronetz.ai via providers.mjs Ollama), 5-panel MMVC stage
  (Model · AI · Module · Controller · View), translated EN/DE/JA/ES/FR
  content, PWA + sitemap + llms.txt + Umami analytics.
- docs/design-system/: canonical mockup bundle (source/index-v2.html for
  splash, source/docs-system.html + preview/ for docs, SPEC.md, tokens).
- docs/scripts/extraction/framework-reference-v2.md: deep framework
  reference (~1.6k lines, file:line citations, every public factory and
  idiom — basis for the LoRA training corpus.
- application/module/ai/: AI module with chat / embed / RAG / agent
  plugins, plus pdoQuery / httpGet / fileRead tools and Modelfile +
  smoke-test in training/.
- application/module/users/: user / ACL / form-factory traits used as the
  reference plugin pattern for the framework docs.
- application/settings/config/database/: schema + seed migrations
  including the AI module tables (200–203).
- Form factory + autogenerator changes the framework-reference-v2 covers.

Production secrets stay out: docs/.env, settings.production.ini and
ai.production.ini are all gitignored (.example files are in tree).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
stephan
2026-05-08 15:22:18 +02:00
parent a60ce90643
commit 48c839d927
662 changed files with 172811 additions and 1 deletions

View File

@@ -0,0 +1,291 @@
/* global React */
const { useState: useEState } = React;
// ============================================================
// TABS
// ============================================================
function Tabs({ tabs }) {
const [active, setActive] = useEState(0);
return (
<div className="tabs">
<div className="tabs-list" role="tablist">
{tabs.map((t, i) => (
<button
key={i}
role="tab"
aria-selected={i === active}
className={"tabs-trigger" + (i === active ? " active" : "")}
onClick={() => setActive(i)}
>
{t.icon && <span className="tabs-icon">{t.icon}</span>}
{t.label}
</button>
))}
</div>
<div className="tabs-panel">
{tabs[active].content}
</div>
</div>
);
}
// ============================================================
// API BLOCK (parameter list)
// ============================================================
function ApiBlock({ signature, params = [], returns }) {
return (
<div className="api-block">
<div className="api-signature">
<span className="tk-fn">{signature.name}</span>
<span className="api-paren">(</span>
{signature.args.map((a, i) => (
<React.Fragment key={i}>
<span className="tk-var">{a.name}</span>
<span className="api-colon">: </span>
<span className="tk-cn">{a.type}</span>
{i < signature.args.length - 1 && <span className="api-comma">, </span>}
</React.Fragment>
))}
<span className="api-paren">)</span>
{signature.returns && (
<>
<span className="api-arrow"> </span>
<span className="tk-cn">{signature.returns}</span>
</>
)}
</div>
<div className="api-section-label">Parameters</div>
<ul className="api-params">
{params.map((p, i) => (
<li key={i} className="api-param">
<div className="api-param-head">
<span className="api-param-name">{p.name}</span>
<span className="api-param-type">{p.type}</span>
{p.required && <span className="api-param-required">required</span>}
{p.default !== undefined && <span className="api-param-default">default: <code>{p.default}</code></span>}
</div>
<div className="api-param-desc">{p.desc}</div>
</li>
))}
</ul>
{returns && (
<>
<div className="api-section-label">Returns</div>
<div className="api-returns">
<span className="tk-cn">{returns.type}</span>
<span className="api-returns-desc"> {returns.desc}</span>
</div>
</>
)}
</div>
);
}
// ============================================================
// CARD GRID (feature cards)
// ============================================================
function CardGrid({ cards }) {
return (
<div className="card-grid">
{cards.map((c, i) => (
<a href="#" key={i} className="feature-card">
<div className="feature-card-icon" style={{ background: c.glow }}>
{c.icon}
</div>
<div className="feature-card-title">{c.title}</div>
<div className="feature-card-desc">{c.desc}</div>
<div className="feature-card-arrow">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M7 17 17 7M7 7h10v10"/>
</svg>
</div>
</a>
))}
</div>
);
}
// ============================================================
// SEARCH MODAL
// ============================================================
function SearchModal() {
const recent = ["Routing", "Form::addInputType", "Migrations"];
const results = [
{ section: "The Framework", title: "Routing", excerpt: "URL-pattern + SEO-URL parsing, soft 404, automatic action lookup.", kind: "page" },
{ section: "The Framework", title: "Routing Soft 404", excerpt: "When a route doesn't match, the dispatcher falls through to a soft 404 view.", kind: "section" },
{ section: "Get Started", title: "Quick Start Define a route", excerpt: "Routes are declared in config/routes.php and matched by URL pattern.", kind: "section" },
{ section: "API", title: "Router::register()", excerpt: "Register a new route at runtime. Supports pattern matching with named parameters.", kind: "api" },
];
return (
<div className="search-modal">
<div className="search-input-wrap">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="search-icon">
<circle cx="11" cy="11" r="8"/>
<path d="m21 21-4.35-4.35"/>
</svg>
<input className="search-input" placeholder="Search docs, API, examples..." defaultValue="rout" />
<kbd className="search-kbd">esc</kbd>
</div>
<div className="search-recent">
<div className="search-section-label">Recent</div>
<div className="search-recent-list">
{recent.map((r, i) => (
<button key={i} className="search-chip">
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
{r}
</button>
))}
</div>
</div>
<div className="search-results">
<div className="search-section-label">Results</div>
{results.map((r, i) => (
<button key={i} className={"search-result" + (i === 0 ? " active" : "")}>
<div className={"search-result-kind kind-" + r.kind}>{r.kind}</div>
<div className="search-result-body">
<div className="search-result-title">
<span className="search-result-section">{r.section}</span>
<span className="search-result-sep"></span>
<span dangerouslySetInnerHTML={{ __html: r.title.replace(/(rout)/i, "<mark>$1</mark>") }} />
</div>
<div className="search-result-excerpt">{r.excerpt}</div>
</div>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="search-result-go">
<path d="M7 17 17 7M7 7h10v10"/>
</svg>
</button>
))}
</div>
<div className="search-footer">
<span><kbd></kbd> navigate</span>
<span><kbd></kbd> open</span>
<span><kbd>K</kbd> close</span>
<div className="search-credit">Powered by <strong>Nibiru Search</strong></div>
</div>
</div>
);
}
// ============================================================
// FAB (floating help button)
// ============================================================
function FAB() {
return (
<div className="fab-wrap">
<button className="fab" aria-label="Help">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
<path d="M12 5v14M5 12h14"/>
</svg>
</button>
<div className="fab-tooltip">Ask AI assistant</div>
</div>
);
}
// ============================================================
// PAGINATION (prev / next page)
// ============================================================
function Pagination({ prev, next }) {
return (
<nav className="page-nav">
{prev ? (
<a href="#" className="page-nav-link page-nav-prev">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="m15 18-6-6 6-6"/></svg>
<div className="page-nav-stack">
<div className="page-nav-label">Previous</div>
<div className="page-nav-title">{prev}</div>
</div>
</a>
) : <div />}
{next ? (
<a href="#" className="page-nav-link page-nav-next">
<div className="page-nav-stack">
<div className="page-nav-label">Next</div>
<div className="page-nav-title">{next}</div>
</div>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="m9 18 6-6-6-6"/></svg>
</a>
) : <div />}
</nav>
);
}
// ============================================================
// MOBILE DRAWER
// ============================================================
function MobileDrawer() {
return (
<div className="mobile-drawer">
<div className="mobile-drawer-header">
<div className="mobile-drawer-brand">
<LotusMark size={26} />
<span>Nibiru</span>
</div>
<button className="mobile-drawer-close" aria-label="Close">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="m18 6-12 12M6 6l12 12"/></svg>
</button>
</div>
<button className="mobile-drawer-search">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
<span>Search</span>
<kbd>K</kbd>
</button>
<Sidebar />
<div className="mobile-drawer-footer">
<button className="mobile-drawer-pill">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41"/></svg>
Theme
</button>
<button className="mobile-drawer-pill">EN</button>
<a href="#" className="mobile-drawer-pill">GitHub </a>
</div>
</div>
);
}
// ============================================================
// 404 / EMPTY STATE
// ============================================================
function NotFound() {
return (
<div className="not-found">
<div className="not-found-orbit">
<svg viewBox="0 0 320 200" width="100%" height="100%" aria-hidden="true">
<defs>
<radialGradient id="lostStar" cx="50%" cy="50%" r="50%">
<stop offset="0%" stopColor="#fff4d9"/>
<stop offset="50%" stopColor="#ffb574"/>
<stop offset="100%" stopColor="#b86bff" stopOpacity="0"/>
</radialGradient>
</defs>
{/* dashed orbit */}
<ellipse cx="160" cy="100" rx="140" ry="60" fill="none" stroke="rgba(184,107,255,0.3)" strokeWidth="1" strokeDasharray="3 6"/>
<ellipse cx="160" cy="100" rx="100" ry="42" fill="none" stroke="rgba(184,107,255,0.18)" strokeWidth="1" strokeDasharray="3 6"/>
{/* central star */}
<circle cx="160" cy="100" r="50" fill="url(#lostStar)"/>
<circle cx="160" cy="100" r="6" fill="#fff4d9"/>
{/* drifting probe (off-orbit) */}
<g transform="translate(280 40)">
<circle r="3" fill="#6ad9ff"/>
<circle r="8" fill="#6ad9ff" opacity="0.2"/>
</g>
<text x="290" y="35" fill="#6ad9ff" fontSize="10" fontFamily="JetBrains Mono">PROBE-404</text>
<line x1="160" y1="100" x2="280" y2="40" stroke="#6ad9ff" strokeWidth="1" strokeDasharray="2 3" opacity="0.4"/>
</svg>
</div>
<div className="not-found-code">404 / off-orbit</div>
<h2 className="not-found-title">This page drifted away from Nibiru.</h2>
<p className="not-found-desc">
The probe couldn't find the page you requested. It may have been moved, renamed, or pulled into a different orbit.
</p>
<div className="not-found-actions">
<a href="#" className="btn btn-primary">Return to docs</a>
<a href="#" className="btn btn-ghost">Open search</a>
</div>
</div>
);
}
Object.assign(window, { Tabs, ApiBlock, CardGrid, SearchModal, FAB, Pagination, MobileDrawer, NotFound });