Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Supabase public browser-safe configuration
NEXT_PUBLIC_SUPABASE_URL="https://your-project.supabase.co"
NEXT_PUBLIC_SUPABASE_ANON_KEY="your-supabase-anon-key"

# Server-only secrets. Never expose these with NEXT_PUBLIC_ prefixes.
SUPABASE_SERVICE_ROLE_KEY="your-server-only-service-role-key"

# Reserved for a later task. Do not call OpenAI in the foundation app.
OPENAI_API_KEY="your-server-only-openai-api-key"
30 changes: 30 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# dependencies
node_modules/
.pnp
.pnp.*

# next.js
.next/
out/

# production
build/
dist/

# env
.env
.env*.local
!.env.example

# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# typescript
*.tsbuildinfo
next-env.d.ts

# system
.DS_Store
14 changes: 14 additions & 0 deletions app/api/health/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { NextResponse } from "next/server";

export const dynamic = "force-dynamic";

export function GET() {
return NextResponse.json({
ok: true,
project: "pandora-memory-engine",
status: "foundation-ready",
memoryEngineImplemented: false,
databaseSchemaImplemented: false,
openAiIntegrationImplemented: false,
});
}
16 changes: 16 additions & 0 deletions app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { AppShell } from "@/components/layout/app-shell";

export default function DashboardPage() {
return (
<AppShell>
<section className="card">
<h2>Dashboard placeholder</h2>
<p>
This page is intentionally limited to project status. No memory database schema,
namespace retrieval, OpenAI calls, AU canon guard, or simulated memory features have
been implemented yet.
</p>
</section>
</AppShell>
);
}
182 changes: 182 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
:root {
color-scheme: light;
--background: #f7f5f1;
--panel: #ffffff;
--panel-muted: #f0ece6;
--text: #181512;
--muted: #6f675f;
--line: #ddd6cc;
--accent: #6f5132;
--accent-strong: #3d2d1d;
}

* {
box-sizing: border-box;
}

html,
body {
margin: 0;
min-height: 100%;
background: var(--background);
color: var(--text);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

a {
color: inherit;
text-decoration: none;
}

button,
input,
textarea,
select {
font: inherit;
}

::selection {
background: #d8c3a6;
}

.app-shell {
display: grid;
grid-template-columns: 280px minmax(0, 1fr);
min-height: 100vh;
}

.sidebar {
border-right: 1px solid var(--line);
background: rgba(255, 255, 255, 0.72);
padding: 28px;
}

.brand {
align-items: center;
display: flex;
gap: 14px;
margin-bottom: 36px;
}

.brand-mark {
align-items: center;
background: var(--accent-strong);
border-radius: 14px;
color: #fff;
display: inline-flex;
font-weight: 700;
height: 44px;
justify-content: center;
width: 44px;
}

.brand-title,
.brand-subtitle,
.eyebrow {
margin: 0;
}

.brand-title {
font-size: 1rem;
font-weight: 700;
}

.brand-subtitle,
.eyebrow,
.card p {
color: var(--muted);
}

.nav-list {
display: grid;
gap: 8px;
}

.nav-link {
border-radius: 12px;
color: var(--muted);
padding: 12px 14px;
}

.nav-link:hover {
background: var(--panel-muted);
color: var(--text);
}

.workspace {
display: flex;
flex-direction: column;
min-width: 0;
}

.topbar {
align-items: center;
border-bottom: 1px solid var(--line);
display: flex;
justify-content: space-between;
padding: 28px 36px;
}

.topbar h1 {
font-size: clamp(1.6rem, 2vw, 2.4rem);
letter-spacing: -0.04em;
margin: 4px 0 0;
}

.eyebrow {
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
}

.status-pill {
background: #ede4d8;
border: 1px solid #d9c7b2;
border-radius: 999px;
color: var(--accent-strong);
font-size: 0.875rem;
font-weight: 700;
padding: 10px 14px;
}

.content {
padding: 36px;
}

.card {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 24px;
box-shadow: 0 24px 70px rgba(48, 39, 30, 0.08);
max-width: 860px;
padding: 32px;
}

.card h2 {
font-size: 1.6rem;
letter-spacing: -0.03em;
margin: 0 0 12px;
}

.card p {
line-height: 1.7;
margin: 0;
}

@media (max-width: 800px) {
.app-shell {
grid-template-columns: 1fr;
}

.sidebar {
border-bottom: 1px solid var(--line);
border-right: 0;
}

.topbar {
align-items: flex-start;
flex-direction: column;
gap: 16px;
}
}
16 changes: 16 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { ReactNode } from "react";
import type { Metadata } from "next";
import "./globals.css";

export const metadata: Metadata = {
title: "Pandora Memory Engine",
description: "Foundation shell for a namespace-isolated memory operating system.",
};

export default function RootLayout({ children }: Readonly<{ children: ReactNode }>) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
16 changes: 16 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { AppShell } from "@/components/layout/app-shell";

export default function HomePage() {
return (
<AppShell>
<section className="card">
<h2>Foundation shell</h2>
<p>
Pandora is currently in foundation mode. This app establishes the Next.js, TypeScript,
Supabase, validation, and layout boundaries required before memory storage, retrieval,
OpenAI integration, or AU canon logic are implemented.
</p>
</section>
</AppShell>
);
}
15 changes: 15 additions & 0 deletions components/layout/app-shell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { ReactNode } from "react";
import { Sidebar } from "./sidebar";
import { Topbar } from "./topbar";

export function AppShell({ children }: Readonly<{ children: ReactNode }>) {
return (
<div className="app-shell">
<Sidebar />
<div className="workspace">
<Topbar />
<main className="content">{children}</main>
</div>
</div>
);
}
27 changes: 27 additions & 0 deletions components/layout/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Link from "next/link";

const navigation = [
{ href: "/dashboard", label: "Dashboard" },
{ href: "/api/health", label: "Health" },
];

export function Sidebar() {
return (
<aside className="sidebar" aria-label="Primary navigation">
<div className="brand">
<span className="brand-mark">P</span>
<div>
<p className="brand-title">Pandora</p>
<p className="brand-subtitle">Memory Engine</p>
</div>
</div>
<nav className="nav-list">
{navigation.map((item) => (
<Link className="nav-link" href={item.href} key={item.href}>
{item.label}
</Link>
))}
</nav>
</aside>
);
}
13 changes: 13 additions & 0 deletions components/layout/topbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export function Topbar() {
return (
<header className="topbar">
<div>
<p className="eyebrow">Foundation</p>
<h1>Pandora Memory Engine</h1>
</div>
<div className="status-pill" aria-label="Project status">
Base app online
</div>
</header>
);
}
17 changes: 17 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { FlatCompat } from "@eslint/eslintrc";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const compat = new FlatCompat({ baseDirectory: __dirname });

const eslintConfig = [
{
ignores: [".next/**", "node_modules/**", "next-env.d.ts"],
},
...compat.extends("next/core-web-vitals", "next/typescript"),
];

export default eslintConfig;
25 changes: 25 additions & 0 deletions lib/security/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createSupabaseServerClient } from "@/lib/supabase/server";

export async function getCurrentUser() {
const supabase = await createSupabaseServerClient();
const {
data: { user },
error,
} = await supabase.auth.getUser();

if (error) {
return null;
}

return user;
}

export async function requireCurrentUser() {
const user = await getCurrentUser();

if (!user) {
throw new Error("Authentication required.");
}

return user;
}
Loading