diff --git a/.agent/AGENTS.md b/.agent/AGENTS.md new file mode 100644 index 0000000..01a1d90 --- /dev/null +++ b/.agent/AGENTS.md @@ -0,0 +1,71 @@ +# AGENTS.md - Instructions for Coding Assistant LLMs + +[![Next.js](https://img.shields.io/badge/Next.js-14-000000?logo=nextdotjs&logoColor=white)](https://nextjs.org/) +[![React](https://img.shields.io/badge/React-18-61DAFB?logo=react&logoColor=white)](https://react.dev/) +[![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/) +[![Tailwind CSS](https://img.shields.io/badge/Tailwind_CSS-3-06B6D4?logo=tailwindcss&logoColor=white)](https://tailwindcss.com/) +[![Python](https://img.shields.io/badge/Python-3.11+-3776ab?logo=python&logoColor=white)](https://www.python.org/) + +> **Purpose**: Authoritative reference for AI assistants (Claude, GPT, Gemini, Copilot, etc.) working in this repository. + +## Table of Contents + +1. [Project Overview](#1-project-overview) +2. [Technical Stack](#2-technical-stack) +3. [Module Boundaries](#3-module-boundaries) +4. [Key Commands](#4-key-commands) +5. [Coding Standards](#5-coding-standards) +6. [Known Constraints](#6-known-constraints) + +## 1. Project Overview + +This is ACFHarbinger's personal website: a statically-exported Next.js blog/knowledge base covering posts, longer-form reports, project write-ups, tool notes, and media, deployed to GitHub Pages at +[acfharbinger.github.io/github-pages](https://acfharbinger.github.io/github-pages/). Content lives as Markdown under `app/content/
/` and is rendered through the App Router; `notebooks/` is a small, separate Python/uv workspace used to run the analysis behind some reports (e.g. audio signal processing, PCVRP) before writing them up. + +## 2. Technical Stack + +| Component | Specification | Notes | +| --- | --- | --- | +| Next.js | 14 (App Router, `output: 'export'`) | Static export deployed to GitHub Pages, `basePath: /github-pages` | +| React / TypeScript | 18 / 5 | `strict: true` in `tsconfig.json` | +| Styling | Tailwind CSS 3 | Config in `tailwind.config.js` | +| Content | Markdown + `gray-matter` / `remark` | Parsed at build time from `app/content/
/` | +| Unit tests | Vitest + Testing Library | `test/unit/`, mirroring `src/components/` | +| Integration tests | Vitest + Testing Library + MSW | `test/integration/` | +| E2E / smoke tests | Cypress | `test/cypress/e2e/`, `test/cypress/smoke/` | +| Notebooks | Python 3.11+, managed via `uv` | `notebooks/`, workspace member of the root `pyproject.toml` | + +## 3. Module Boundaries + +- `app/` — Next.js App Router: routes, layouts, and Markdown content under `app/content/
/` (`posts`, `reports`, `projects`, `tools`, `media`, `about`, `other`). +- `src/components/` — presentational and layout React components consumed by `app/`. Business logic (content loading/parsing) belongs in `lib/`, not inline in components. +- `lib/` — server-side helpers (Markdown loading/parsing, front-matter handling) used by `app/` at build time. +- `notebooks/` — independent Python/uv workspace for exploratory analysis backing written reports. Not part of the Next.js build; never imported from `src/`/`app/`. +- `public/` — static assets served as-is. +- `infra/global/` — optional external/public-facing deploy and host tooling (docker, k8s, helm, terraform, ansible). Not used by the default GitHub Pages workflow. +- `infra/cloud/` — managed cloud static-host configs (AWS, Azure Pipelines, Firebase, Serverless). +- `infra/server/` — standalone nginx and Envoy reverse-proxy configs. +- `infra/private/` — internal developer-only infra experiments (e.g. webpack, wordpress). + +## 4. Key Commands + +| Command | Purpose | +| --- | --- | +| `npm run dev` | Local dev server | +| `npm run build` | Static export to `out/` | +| `npm run lint` | ESLint (Next.js config) | +| `npm test` / `npm run test:watch` | Vitest: unit (`test/unit/`) + integration (`test/integration/`) | +| `npm run cypress:run` / `npm run cypress:smoke` | Cypress e2e/smoke (against a running build/dev server) | +| `cd notebooks && uv sync --extra dev` | Set up the notebooks Python environment | + +## 5. Coding Standards + +- Follow the per-topic rules in [`.agent/rules/`](rules/) (`typescript_react.md`, `python.md`, plus the language-agnostic ones). +- Prefer small, reviewable diffs. Do not reformat files unrelated to the change. +- New components get a Vitest unit test in `test/unit/`; multi-component interactions get an integration test in `test/integration/` (mock any network calls with MSW); new user-facing flows get a Cypress spec in `test/cypress/e2e/`. +- Never commit secrets. This site has no runtime secrets today — flag it clearly if a change would introduce one. + +## 6. Known Constraints + +- The site is a fully static export (`output: 'export'`) — no server-side code, API routes, or runtime environment variables beyond the build-time `NEXT_PUBLIC_BASE_PATH`. +- `notebooks/` is exploratory/research tooling, not covered by the main CI build; it has its own lint/test story via `uv`. diff --git a/.agent/chat/claude_subagent_delegation.md b/.agent/chat/claude_subagent_delegation.md new file mode 100644 index 0000000..3e0fd3f --- /dev/null +++ b/.agent/chat/claude_subagent_delegation.md @@ -0,0 +1,28 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: CLAUDE + +**Identity & Capability:** +You can deploy a Claude AI subagent via the local terminal using the `claude` CLI command. Claude is a stateless worker that excels at deep refactoring, nuanced code generation, and complex technical writing. + +**When to Delegate:** +Invoke the Claude subagent for: +* **Deep Refactoring:** Restructuring a React/TypeScript component tree in `src/components/` without changing rendered output. +* **UI/Frontend Generation:** Generating structured code for complex layouts (e.g., a new content-listing or data-visualization component). +* **Granular Code Review:** Performing rigorous audits of a notebook's analysis logic before it's cited in a report. + +**Execution Syntax:** +Run the command in your shell, wrapping the prompt in strong quotes. +`claude 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to Claude):** +1. **Complete Independence:** Claude cannot read your memory. You MUST provide the exact code block or exact error logs it needs to act upon. +2. **ReAct / CoT Triggers:** Instruct Claude to use `` XML blocks to plan its refactoring steps before outputting code. +3. **Strict Boundaries:** Specify exact input and output formats (e.g., "Output ONLY valid TypeScript code inside a single markdown block"). + +**Example Usage:** +`claude 'Act as an expert React/TypeScript developer. Refactor the following PostWrapper component so it shares its Markdown-rendering logic with ReportWrapper via a common hook, without changing rendered output. Wrap your reasoning in tags, then provide the refactored code. Code to refactor: [INSERT_CODE_HERE]'` + +**Failure Modes to Avoid:** +* **Do not** include single quotes inside the prompt string without escaping them. +* **Do not** delegate tasks that require multi-turn conversational context. +--- \ No newline at end of file diff --git a/.agent/chat/gemini_subagent_delegation.md b/.agent/chat/gemini_subagent_delegation.md new file mode 100644 index 0000000..8c4de37 --- /dev/null +++ b/.agent/chat/gemini_subagent_delegation.md @@ -0,0 +1,28 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: GEMINI + +**Identity & Capability:** +You have the authority to spawn a Gemini AI subagent via the terminal using the `agy` CLI command. Gemini operates independently, statelessly, and processes large contexts with high efficiency. + +**When to Delegate:** +Invoke the Gemini subagent for: +* **Data Wrangling:** Standardizing a notebook's data transformations or parsing large JSON/log/CSV inputs for a report. +* **Front-Matter/Content Boilerplate:** Generating consistent Markdown front-matter and stub pages when adding a new `app/content/
/` entry. +* **Long-Context Extraction:** Pulling structured summaries or key figures out of a long research write-up under `docs/research/`. + +**Execution Syntax:** +Execute the command in your terminal. Ensure the prompt is enclosed in single quotes. +`agy 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to Gemini):** +1. **Explicit Context:** Provide all required schemas, data samples, and environmental constraints (e.g., Linux, KDE, specific GPU hardware). +2. **Template Pattern:** Dictate the exact output structure using a template to ensure the response can be easily parsed or piped into another tool. +3. **Action-Oriented Verbs:** Start instructions with clear directives like "Analyze," "Generate," or "Extract." + +**Example Usage:** +`agy 'Act as an expert technical writer. Given the following notebook analysis output (summary stats + a chart description), draft the "Results" section of a Markdown report for app/content/reports/. Constraints: 1. Match the tone of the site's existing reports. 2. Output only the Markdown section. Context: [INSERT_ANALYSIS_OUTPUT]'` + +**Failure Modes to Avoid:** +* **Do not** use unescaped single quotes in the `agy` execution string. +* **Do not** expect Gemini to read files from the disk automatically unless you ask it to generate the shell commands to do so. +--- \ No newline at end of file diff --git a/.agent/chat/grok_subagent_delegation.md b/.agent/chat/grok_subagent_delegation.md new file mode 100644 index 0000000..eaf9250 --- /dev/null +++ b/.agent/chat/grok_subagent_delegation.md @@ -0,0 +1,33 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: GROK + +**Identity & Capability:** +You can deploy a Grok AI subagent via the local terminal using the `grok` CLI command (Grok Build by xAI). Grok is a tool-using coding agent that can explore codebases, edit files, run shell commands, and implement multi-step software engineering tasks. It does not share your context window; treat it as an independent worker. + +**When to Delegate:** +Invoke the Grok subagent for: +* **End-to-End Implementation:** Building features, fixing bugs, or applying multi-file refactors with tests and commits. +* **Codebase Exploration:** Navigating large repositories, tracing call graphs, and summarizing architecture before you act. +* **Agentic Tool Work:** Tasks that benefit from iterative shell/build/test loops rather than pure text generation. +* **Real-Time / Web-Aware Reasoning:** Problems that need up-to-date context, search, or alternative high-level approaches when you are stuck. + +**Execution Syntax:** +Run the command in your shell, wrapping the prompt in strong quotes to prevent shell evaluation errors. Prefer single-turn / headless mode so the subagent prints a result and exits: +`grok -p 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +For longer agentic work that may need tools and multiple turns: +`grok --always-approve -p 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to Grok):** +1. **Complete Independence:** Grok does not see your conversation. Include paths, constraints, acceptance criteria, and any required snippets or error logs in the prompt. +2. **Actionable Scope:** State the working directory, which files may change, and what "done" looks like (e.g., "tests pass", "output only the diff summary"). +3. **Strict Boundaries:** Specify format and limits (e.g., "Do not open a PR", "Output ONLY a bullet list of findings", "Modify only files under src/"). + +**Example Usage:** +`grok -p 'Act as an expert systems engineer. In the current repository, locate the authentication middleware, identify why refresh tokens are rejected after 24h, and propose a minimal fix. Constraints: 1. Do not modify files. 2. Return a short root-cause analysis and a concrete patch suggestion in a single markdown code block.'` + +**Failure Modes to Avoid:** +* **Do not** use unescaped single quotes inside the `grok` command string. +* **Do not** assume Grok knows your prior conversation or unstated project goals. +* **Do not** nest quotes improperly (e.g., `grok -p 'He said 'hello''`). +--- diff --git a/.agent/claude/chatgpt_subagent_delegation.md b/.agent/claude/chatgpt_subagent_delegation.md new file mode 100644 index 0000000..8fb228a --- /dev/null +++ b/.agent/claude/chatgpt_subagent_delegation.md @@ -0,0 +1,29 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: CHATGPT + +**Identity & Capability:** +You can orchestrate a ChatGPT AI subagent via your local terminal using the `chatgpt` CLI command. ChatGPT acts as a stateless, highly capable reasoning engine. It does not share your context window. + +**When to Delegate:** +Invoke the ChatGPT subagent for: +* **Mathematical Formulations:** Drafting formal definitions or notation for a report's math section (e.g. the PCVRP report under `app/content/reports/`). +* **Literature & Concept Mapping:** Summarizing background material or related work to cite in a report. +* **Creative Brainstorming:** Generating varied framings for a post/report before committing to a structure. + +**Execution Syntax:** +Run the command in your shell, wrapping the prompt in strong quotes to prevent shell evaluation errors. +`chatgpt 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to ChatGPT):** +1. **Zero-Shot Context:** You MUST include all necessary definitions, constraints, and current state. +2. **Constraint Pattern:** Explicitly list what ChatGPT must *not* do to keep the response focused and token-efficient. +3. **Role Definition:** Always assign ChatGPT a clear persona (e.g., "Act as a PhD-level Operations Research scientist"). + +**Example Usage:** +`chatgpt 'Act as an Operations Research expert. I am writing a report on the Periodic Capacitated Vehicle Routing Problem (PCVRP). Provide the formal mathematical formulation for the objective function minimizing total travel cost over a multi-day horizon. Rules: 1. Use standard OR notation. 2. Define all variables clearly. 3. Output ONLY the formulation and variable definitions in plain text.'` + +**Failure Modes to Avoid:** +* **Do not** use unescaped single quotes inside the `chatgpt` command string. +* **Do not** assume ChatGPT knows our current project state. +* **Do not** delegate tasks requiring direct file manipulation. +--- \ No newline at end of file diff --git a/.agent/claude/gemini_subagent_delegation.md b/.agent/claude/gemini_subagent_delegation.md new file mode 100644 index 0000000..d0b8204 --- /dev/null +++ b/.agent/claude/gemini_subagent_delegation.md @@ -0,0 +1,29 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: GEMINI + +**Identity & Capability:** +You have the ability to spawn an independent Gemini AI subagent via your local terminal using the `agy` CLI command. Gemini does not share your context window; it acts as a stateless, highly capable assistant. + +**When to Delegate:** +Invoke the Gemini subagent for: +* **Alternative Reasoning:** Generating a second opinion on a notebook's analysis approach before it's written up as a report. +* **Parallel Processing:** Delegating isolated sub-tasks (e.g., drafting a regex, summarizing a log file) while you focus on the main change. +* **Long-Context Summarization:** Leveraging Gemini's large context window to distill a long research doc under `docs/research/` into a post-sized outline. + +**Execution Syntax:** +Run the command in your shell, wrapping the prompt in strong quotes to prevent shell evaluation errors. +`agy 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to Gemini):** +1. **Zero-Shot Context:** You MUST include all necessary code, logs, or context in your string. Gemini cannot see your current workspace unless explicitly provided in the prompt. +2. **Explicit Formatting:** Tell Gemini exactly how to output the response (e.g., "Return ONLY raw JSON," or "Provide a Markdown table"). +3. **Role Definition:** Assign Gemini a role if helpful (e.g., "Act as an expert technical editor..."). + +**Example Usage:** +`agy 'Act as an expert in signal processing. Review the following Python analysis code from notebooks/ and identify potential correctness issues in the filtering step. Return only a bulleted list of 3 specific issues: [INSERT_LOGIC_HERE]'` + +**Failure Modes to Avoid:** +* **Do not** use unescaped single quotes inside the `agy` command string. +* **Do not** assume Gemini knows the history of our conversation. +* **Do not** delegate tasks that require writing files directly, unless you instruct Gemini to output the exact terminal commands for you to run. +--- \ No newline at end of file diff --git a/.agent/claude/grok_subagent_delegation.md b/.agent/claude/grok_subagent_delegation.md new file mode 100644 index 0000000..eaf9250 --- /dev/null +++ b/.agent/claude/grok_subagent_delegation.md @@ -0,0 +1,33 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: GROK + +**Identity & Capability:** +You can deploy a Grok AI subagent via the local terminal using the `grok` CLI command (Grok Build by xAI). Grok is a tool-using coding agent that can explore codebases, edit files, run shell commands, and implement multi-step software engineering tasks. It does not share your context window; treat it as an independent worker. + +**When to Delegate:** +Invoke the Grok subagent for: +* **End-to-End Implementation:** Building features, fixing bugs, or applying multi-file refactors with tests and commits. +* **Codebase Exploration:** Navigating large repositories, tracing call graphs, and summarizing architecture before you act. +* **Agentic Tool Work:** Tasks that benefit from iterative shell/build/test loops rather than pure text generation. +* **Real-Time / Web-Aware Reasoning:** Problems that need up-to-date context, search, or alternative high-level approaches when you are stuck. + +**Execution Syntax:** +Run the command in your shell, wrapping the prompt in strong quotes to prevent shell evaluation errors. Prefer single-turn / headless mode so the subagent prints a result and exits: +`grok -p 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +For longer agentic work that may need tools and multiple turns: +`grok --always-approve -p 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to Grok):** +1. **Complete Independence:** Grok does not see your conversation. Include paths, constraints, acceptance criteria, and any required snippets or error logs in the prompt. +2. **Actionable Scope:** State the working directory, which files may change, and what "done" looks like (e.g., "tests pass", "output only the diff summary"). +3. **Strict Boundaries:** Specify format and limits (e.g., "Do not open a PR", "Output ONLY a bullet list of findings", "Modify only files under src/"). + +**Example Usage:** +`grok -p 'Act as an expert systems engineer. In the current repository, locate the authentication middleware, identify why refresh tokens are rejected after 24h, and propose a minimal fix. Constraints: 1. Do not modify files. 2. Return a short root-cause analysis and a concrete patch suggestion in a single markdown code block.'` + +**Failure Modes to Avoid:** +* **Do not** use unescaped single quotes inside the `grok` command string. +* **Do not** assume Grok knows your prior conversation or unstated project goals. +* **Do not** nest quotes improperly (e.g., `grok -p 'He said 'hello''`). +--- diff --git a/.agent/gemini/chatgpt_subagent_delegation.md b/.agent/gemini/chatgpt_subagent_delegation.md new file mode 100644 index 0000000..0845c68 --- /dev/null +++ b/.agent/gemini/chatgpt_subagent_delegation.md @@ -0,0 +1,30 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: CHATGPT + +**Identity & Capability:** +You are equipped to launch a ChatGPT AI subagent via the `chatgpt` command. ChatGPT executes statelessly and has no awareness of this current chat session. + +**When to Delegate:** +Invoke the ChatGPT subagent for: +* **Content Drafting:** Drafting or tightening prose for a blog post/report before it's polished and placed under `app/content/`. +* **Documentation & Abstraction:** Generating clear, high-level summaries of a notebook's analysis for the write-up that cites it. +* **Alternative Paradigms:** Asking for a completely different framing of a post/report when the current draft feels stuck. + +**Execution Syntax:** +Execute the command in your terminal environment. Always enclose the prompt in single quotes. +`chatgpt 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to ChatGPT):** +1. **Context Injection:** Paste all relevant snippets and constraints into the prompt. +2. **Structured Output:** Use the Template Pattern. Define exactly how the output should look using a mock structure. +3. **Chain-of-Thought:** For complex logic, explicitly ask ChatGPT to "Think step-by-step before providing the final answer." + +**Example Usage:** +`chatgpt 'Act as a technical editor. Below is a draft section of a blog report on audio signal processing. Think step-by-step about where the explanation loses a non-specialist reader before tightening it. Output format: +## Reasoning: [Step-by-step thoughts] +## Revised Section: [Rewritten Markdown]'` + +**Failure Modes to Avoid:** +* **Do not** nest quotes improperly (e.g., `chatgpt 'He said 'hello''`). +* **Do not** use ambiguous instructions; be explicit about the domain. +--- \ No newline at end of file diff --git a/.agent/gemini/claude_subagent_delegation.md b/.agent/gemini/claude_subagent_delegation.md new file mode 100644 index 0000000..a9cdf8f --- /dev/null +++ b/.agent/gemini/claude_subagent_delegation.md @@ -0,0 +1,29 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: CLAUDE + +**Identity & Capability:** +You are equipped with a CLI tool to orchestrate a Claude AI subagent via the `claude` command. Claude executes statelessly and has no awareness of this current chat session or your previous outputs. + +**When to Delegate:** +Invoke the Claude subagent for: +* **Deep Refactoring:** Restructuring a React/TypeScript component tree in `src/components/` without changing rendered output. +* **UI/Frontend Generation:** Generating structured code for visually complex layouts (e.g., a new content-listing or data-visualization component). +* **Code Review:** Performing rigorous, independent audits of a notebook's analysis logic before it's cited in a report. + +**Execution Syntax:** +Execute the command in your terminal environment. Always enclose the prompt in single quotes to protect shell formatting. +`claude 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to Claude):** +1. **Context Injection:** You MUST paste all relevant snippets, constraints, and current state into the prompt. +2. **Strict Boundaries:** Clearly define what Claude should NOT do to save processing time (e.g., "Do not write explanations, output only the refactored component file"). +3. **Step-by-Step Prompting:** For complex tasks, instruct Claude to use a `` XML block before providing the final answer. + +**Example Usage:** +`claude 'You are an expert TypeScript/React developer. Below is the shape of the front-matter for a report under app/content/reports/. Write a well-typed TS interface for it, and generate a generic React component to render its metadata card. Wrap your reasoning in tags and output the code in a single markdown block. Context: [INSERT_FRONTMATTER_SHAPE_HERE]'` + +**Failure Modes to Avoid:** +* **Do not** nest quotes improperly (e.g., `claude 'He said 'hello''`). +* **Do not** ask Claude to perform actions it cannot do (like interacting with your local file system directly). You must parse its text output and perform the file operations yourself. +* **Do not** use ambiguous instructions; quantify your requests (e.g., "Provide exactly 2 solutions"). +--- \ No newline at end of file diff --git a/.agent/gemini/grok_subagent_delegation.md b/.agent/gemini/grok_subagent_delegation.md new file mode 100644 index 0000000..eaf9250 --- /dev/null +++ b/.agent/gemini/grok_subagent_delegation.md @@ -0,0 +1,33 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: GROK + +**Identity & Capability:** +You can deploy a Grok AI subagent via the local terminal using the `grok` CLI command (Grok Build by xAI). Grok is a tool-using coding agent that can explore codebases, edit files, run shell commands, and implement multi-step software engineering tasks. It does not share your context window; treat it as an independent worker. + +**When to Delegate:** +Invoke the Grok subagent for: +* **End-to-End Implementation:** Building features, fixing bugs, or applying multi-file refactors with tests and commits. +* **Codebase Exploration:** Navigating large repositories, tracing call graphs, and summarizing architecture before you act. +* **Agentic Tool Work:** Tasks that benefit from iterative shell/build/test loops rather than pure text generation. +* **Real-Time / Web-Aware Reasoning:** Problems that need up-to-date context, search, or alternative high-level approaches when you are stuck. + +**Execution Syntax:** +Run the command in your shell, wrapping the prompt in strong quotes to prevent shell evaluation errors. Prefer single-turn / headless mode so the subagent prints a result and exits: +`grok -p 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +For longer agentic work that may need tools and multiple turns: +`grok --always-approve -p 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to Grok):** +1. **Complete Independence:** Grok does not see your conversation. Include paths, constraints, acceptance criteria, and any required snippets or error logs in the prompt. +2. **Actionable Scope:** State the working directory, which files may change, and what "done" looks like (e.g., "tests pass", "output only the diff summary"). +3. **Strict Boundaries:** Specify format and limits (e.g., "Do not open a PR", "Output ONLY a bullet list of findings", "Modify only files under src/"). + +**Example Usage:** +`grok -p 'Act as an expert systems engineer. In the current repository, locate the authentication middleware, identify why refresh tokens are rejected after 24h, and propose a minimal fix. Constraints: 1. Do not modify files. 2. Return a short root-cause analysis and a concrete patch suggestion in a single markdown code block.'` + +**Failure Modes to Avoid:** +* **Do not** use unescaped single quotes inside the `grok` command string. +* **Do not** assume Grok knows your prior conversation or unstated project goals. +* **Do not** nest quotes improperly (e.g., `grok -p 'He said 'hello''`). +--- diff --git a/.agent/grok/chatgpt_subagent_delegation.md b/.agent/grok/chatgpt_subagent_delegation.md new file mode 100644 index 0000000..8fb228a --- /dev/null +++ b/.agent/grok/chatgpt_subagent_delegation.md @@ -0,0 +1,29 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: CHATGPT + +**Identity & Capability:** +You can orchestrate a ChatGPT AI subagent via your local terminal using the `chatgpt` CLI command. ChatGPT acts as a stateless, highly capable reasoning engine. It does not share your context window. + +**When to Delegate:** +Invoke the ChatGPT subagent for: +* **Mathematical Formulations:** Drafting formal definitions or notation for a report's math section (e.g. the PCVRP report under `app/content/reports/`). +* **Literature & Concept Mapping:** Summarizing background material or related work to cite in a report. +* **Creative Brainstorming:** Generating varied framings for a post/report before committing to a structure. + +**Execution Syntax:** +Run the command in your shell, wrapping the prompt in strong quotes to prevent shell evaluation errors. +`chatgpt 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to ChatGPT):** +1. **Zero-Shot Context:** You MUST include all necessary definitions, constraints, and current state. +2. **Constraint Pattern:** Explicitly list what ChatGPT must *not* do to keep the response focused and token-efficient. +3. **Role Definition:** Always assign ChatGPT a clear persona (e.g., "Act as a PhD-level Operations Research scientist"). + +**Example Usage:** +`chatgpt 'Act as an Operations Research expert. I am writing a report on the Periodic Capacitated Vehicle Routing Problem (PCVRP). Provide the formal mathematical formulation for the objective function minimizing total travel cost over a multi-day horizon. Rules: 1. Use standard OR notation. 2. Define all variables clearly. 3. Output ONLY the formulation and variable definitions in plain text.'` + +**Failure Modes to Avoid:** +* **Do not** use unescaped single quotes inside the `chatgpt` command string. +* **Do not** assume ChatGPT knows our current project state. +* **Do not** delegate tasks requiring direct file manipulation. +--- \ No newline at end of file diff --git a/.agent/grok/claude_subagent_delegation.md b/.agent/grok/claude_subagent_delegation.md new file mode 100644 index 0000000..3e0fd3f --- /dev/null +++ b/.agent/grok/claude_subagent_delegation.md @@ -0,0 +1,28 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: CLAUDE + +**Identity & Capability:** +You can deploy a Claude AI subagent via the local terminal using the `claude` CLI command. Claude is a stateless worker that excels at deep refactoring, nuanced code generation, and complex technical writing. + +**When to Delegate:** +Invoke the Claude subagent for: +* **Deep Refactoring:** Restructuring a React/TypeScript component tree in `src/components/` without changing rendered output. +* **UI/Frontend Generation:** Generating structured code for complex layouts (e.g., a new content-listing or data-visualization component). +* **Granular Code Review:** Performing rigorous audits of a notebook's analysis logic before it's cited in a report. + +**Execution Syntax:** +Run the command in your shell, wrapping the prompt in strong quotes. +`claude 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to Claude):** +1. **Complete Independence:** Claude cannot read your memory. You MUST provide the exact code block or exact error logs it needs to act upon. +2. **ReAct / CoT Triggers:** Instruct Claude to use `` XML blocks to plan its refactoring steps before outputting code. +3. **Strict Boundaries:** Specify exact input and output formats (e.g., "Output ONLY valid TypeScript code inside a single markdown block"). + +**Example Usage:** +`claude 'Act as an expert React/TypeScript developer. Refactor the following PostWrapper component so it shares its Markdown-rendering logic with ReportWrapper via a common hook, without changing rendered output. Wrap your reasoning in tags, then provide the refactored code. Code to refactor: [INSERT_CODE_HERE]'` + +**Failure Modes to Avoid:** +* **Do not** include single quotes inside the prompt string without escaping them. +* **Do not** delegate tasks that require multi-turn conversational context. +--- \ No newline at end of file diff --git a/.agent/grok/gemini_subagent_delegation.md b/.agent/grok/gemini_subagent_delegation.md new file mode 100644 index 0000000..8c4de37 --- /dev/null +++ b/.agent/grok/gemini_subagent_delegation.md @@ -0,0 +1,28 @@ +--- +### SUBAGENT DELEGATION PROTOCOL: GEMINI + +**Identity & Capability:** +You have the authority to spawn a Gemini AI subagent via the terminal using the `agy` CLI command. Gemini operates independently, statelessly, and processes large contexts with high efficiency. + +**When to Delegate:** +Invoke the Gemini subagent for: +* **Data Wrangling:** Standardizing a notebook's data transformations or parsing large JSON/log/CSV inputs for a report. +* **Front-Matter/Content Boilerplate:** Generating consistent Markdown front-matter and stub pages when adding a new `app/content/
/` entry. +* **Long-Context Extraction:** Pulling structured summaries or key figures out of a long research write-up under `docs/research/`. + +**Execution Syntax:** +Execute the command in your terminal. Ensure the prompt is enclosed in single quotes. +`agy 'YOUR_COMPREHENSIVE_PROMPT_HERE'` + +**Subagent Prompting Rules (How to talk to Gemini):** +1. **Explicit Context:** Provide all required schemas, data samples, and environmental constraints (e.g., Linux, KDE, specific GPU hardware). +2. **Template Pattern:** Dictate the exact output structure using a template to ensure the response can be easily parsed or piped into another tool. +3. **Action-Oriented Verbs:** Start instructions with clear directives like "Analyze," "Generate," or "Extract." + +**Example Usage:** +`agy 'Act as an expert technical writer. Given the following notebook analysis output (summary stats + a chart description), draft the "Results" section of a Markdown report for app/content/reports/. Constraints: 1. Match the tone of the site's existing reports. 2. Output only the Markdown section. Context: [INSERT_ANALYSIS_OUTPUT]'` + +**Failure Modes to Avoid:** +* **Do not** use unescaped single quotes in the `agy` execution string. +* **Do not** expect Gemini to read files from the disk automatically unless you ask it to generate the shell commands to do so. +--- \ No newline at end of file diff --git a/.agent/prompts/architecture_analysis.md b/.agent/prompts/architecture_analysis.md new file mode 100644 index 0000000..e3fba07 --- /dev/null +++ b/.agent/prompts/architecture_analysis.md @@ -0,0 +1,8 @@ +# Prompt: Architecture Analysis + +Given a request to analyze or propose architecture changes: + +1. Read [`.agent/AGENTS.md`](../AGENTS.md) §3 for current module boundaries (`app/`, `src/components/`, `lib/`, `notebooks/`). +2. Identify which of those boundaries the change affects, and whether it's compatible with a static export (`output: 'export'`) — no server runtime. +3. Present trade-offs (at least two options) rather than a single prescriptive answer, unless the choice is clear-cut. +4. Note the migration cost for existing content/pages if the proposal changes how content is loaded or routed. diff --git a/.agent/prompts/debug.md b/.agent/prompts/debug.md new file mode 100644 index 0000000..a7f7364 --- /dev/null +++ b/.agent/prompts/debug.md @@ -0,0 +1,9 @@ +# Prompt: Debug an Issue + +Given a bug report: + +1. Reproduce it first — do not attempt a fix from the description alone. +2. Follow `.agent/workflows/error_debug.md`. +3. Identify the minimal root cause; explain why the current code produces the wrong result. +4. Fix and add a regression test. +5. Report back with: root cause, fix, and what the regression test now guards against. diff --git a/.agent/prompts/documentation_update.md b/.agent/prompts/documentation_update.md new file mode 100644 index 0000000..a420a3a --- /dev/null +++ b/.agent/prompts/documentation_update.md @@ -0,0 +1,8 @@ +# Prompt: Documentation Update + +Given a request to update documentation: + +1. Identify every doc surface affected: `README.md`, `docs/research/*.md`, `.agent/AGENTS.md`, inline comments. +2. Match the existing tone and structure of the surrounding document — don't introduce a new format for one section. +3. Verify any commands/code examples actually run against the current codebase before including them. +4. Check for now-stale links elsewhere in the repo that reference the changed section. diff --git a/.agent/prompts/feature_implementation.md b/.agent/prompts/feature_implementation.md new file mode 100644 index 0000000..73c6767 --- /dev/null +++ b/.agent/prompts/feature_implementation.md @@ -0,0 +1,9 @@ +# Prompt: Feature Implementation + +Given a feature request: + +1. Restate the feature as concrete acceptance criteria. +2. Identify which layer(s) it touches (`app/` routing/content, `src/components/`, `lib/`) and read [`.agent/rules/typescript_react.md`](../rules/typescript_react.md). +3. Propose the smallest design that satisfies the criteria; flag any ambiguity as a question rather than assuming. +4. Implement with tests written alongside the code, not after (Vitest for components/logic in `test/unit/`, an MSW-backed integration test in `test/integration/` for multi-component flows, Cypress for a new user-facing flow). +5. Confirm the change still builds as a static export (`npm run build`). diff --git a/.agent/prompts/master_context.md b/.agent/prompts/master_context.md new file mode 100644 index 0000000..6feec50 --- /dev/null +++ b/.agent/prompts/master_context.md @@ -0,0 +1,9 @@ +# Master Context Prompt + +You are working in ACFHarbinger's personal website: a statically-exported Next.js/React/TypeScript blog. Before making changes: + +1. Read [`.agent/AGENTS.md`](../AGENTS.md) for the project's stack, module boundaries, and coding standards. +2. Follow the per-topic rules in [`.agent/rules/`](../rules/) and the workflow in [`.agent/workflows/`](../workflows/) matching the task type. +3. Remember the site is a fully static export deployed to GitHub Pages — no server runtime, no API routes. + +If a requested change implies something the site can't do statically (a server-side secret, a database, a live API route), say so rather than inventing a workaround. diff --git a/.agent/prompts/refactory_safety.md b/.agent/prompts/refactory_safety.md new file mode 100644 index 0000000..bfadb00 --- /dev/null +++ b/.agent/prompts/refactory_safety.md @@ -0,0 +1,9 @@ +# Prompt: Safe Refactor + +Given a refactor request: + +1. Confirm test coverage exists for the code being touched; add characterization tests first if not. +2. Follow `.agent/workflows/code_refactor.md` — mechanical changes only, no behavior changes in the same commit. +3. Run the full test suite for the affected module(s) before and after. +4. List every call site updated, so the diff is auditable against the stated scope. +5. Flag anything that looked risky enough to warrant a human second look. diff --git a/.agent/rules/code_refactor.md b/.agent/rules/code_refactor.md new file mode 100644 index 0000000..a181657 --- /dev/null +++ b/.agent/rules/code_refactor.md @@ -0,0 +1,7 @@ +# Code Refactoring Rules + +- Refactors must not change observable behavior in the same change as a behavior fix — separate the two into different commits/PRs. +- Run the existing test suite before and after; a refactor that requires rewriting tests to pass is probably a behavior change in disguise. +- Prefer extracting/renaming over rewriting from scratch — smaller diffs are easier to review and revert. +- Remove dead code you find along the way instead of leaving it "just in case"; git history is the safety net. +- Don't introduce a new abstraction for a pattern used only once or twice — wait for a third occurrence. diff --git a/.agent/rules/code_review.md b/.agent/rules/code_review.md new file mode 100644 index 0000000..a1fc9cf --- /dev/null +++ b/.agent/rules/code_review.md @@ -0,0 +1,7 @@ +# Code Review Rules + +- Review for correctness first, then simplification, then style — don't bikeshed formatting on a PR with a real bug. +- Flag missing test coverage for new branches/edge cases explicitly, with a concrete failing scenario, not just "add more tests." +- Call out security-sensitive changes (auth, input parsing, secrets, deserialization) even if outside the PR's stated scope. +- Prefer suggesting the specific fix over describing the problem abstractly — reviewers should be able to act on a comment without re-deriving it. +- Approve when the change is a net improvement, not only when it is perfect. diff --git a/.agent/rules/error_debug.md b/.agent/rules/error_debug.md new file mode 100644 index 0000000..bc90c0f --- /dev/null +++ b/.agent/rules/error_debug.md @@ -0,0 +1,7 @@ +# Error Handling & Debugging Rules + +- Reproduce the bug with a failing test before fixing it, when practical; the test stays in the suite afterward as a regression guard. +- Fix root causes, not symptoms — do not silence an exception/warning just to make a run "pass." +- Include enough context in error messages to debug from logs alone: what operation failed, on what input, and why. +- When a fix touches shared/critical code, check for other callers relying on the old (broken) behavior before changing it. +- Never use bare `except:`/catch-all handlers that swallow errors silently; log or re-raise. diff --git a/.agent/rules/gui_dev.md b/.agent/rules/gui_dev.md new file mode 100644 index 0000000..f9c5a17 --- /dev/null +++ b/.agent/rules/gui_dev.md @@ -0,0 +1,7 @@ +# GUI Development Rules + +- Keep UI components presentation-only; business logic belongs in a service/store layer that the UI calls into, so it stays testable without a rendered UI. +- Every new interactive control needs a keyboard-accessible path and an accessible name/label — not just a mouse affordance. +- Long-running work (>100ms) must run off the UI thread (worker thread, async task, or backend call) with visible progress/cancellation. +- Match the existing design system/component library before introducing a new one-off style. +- Add at least one component/integration test per new screen or panel. diff --git a/.agent/rules/python.md b/.agent/rules/python.md new file mode 100644 index 0000000..d2b8655 --- /dev/null +++ b/.agent/rules/python.md @@ -0,0 +1,8 @@ +# Python Rules (notebooks/) + +- Target Python 3.11+. Use `uv` for dependency management (`uv sync --extra dev`, `uv add`, `uv run`) inside `notebooks/`. +- Format and lint with `ruff` (`ruff format`, `ruff check --fix`). Do not hand-format code that `ruff format` would rewrite. +- Type-check with `mypy` where practical; notebooks are exploratory, so this is advisory, not blocking. +- Strip notebook output before committing (`nbstripout`, wired via `.pre-commit-config.yaml`) — outputs bloat diffs and can leak local file paths. +- Keep reusable logic in plain `.py` modules next to the notebook that uses it; don't duplicate the same analysis code across multiple notebooks. +- `notebooks/` is a standalone workspace, not part of the Next.js build — never import from it in `src/`/`app/`, and don't add it as a build dependency. diff --git a/.agent/rules/reasoning_planning.md b/.agent/rules/reasoning_planning.md new file mode 100644 index 0000000..1de1d40 --- /dev/null +++ b/.agent/rules/reasoning_planning.md @@ -0,0 +1,7 @@ +# Reasoning & Planning Rules + +- Before editing more than one file, state the plan in one short paragraph: what changes, why, and what could break. +- Prefer the smallest change that fully solves the stated problem; do not bundle unrelated refactors into the same change. +- When a task is ambiguous, ask a clarifying question rather than guessing at scope — especially for anything destructive or hard to reverse. +- Re-read the relevant code before editing it, even if it was read earlier in the session; assumptions about file contents go stale. +- When a plan changes mid-task, say so explicitly rather than silently pivoting. diff --git a/.agent/rules/test_writing.md b/.agent/rules/test_writing.md new file mode 100644 index 0000000..4338b19 --- /dev/null +++ b/.agent/rules/test_writing.md @@ -0,0 +1,7 @@ +# Test Writing Rules + +- One assertion concept per test; name tests after the behavior under test (`test___`), not the implementation. +- Cover the happy path, at least one edge case, and at least one failure case for every new public function. +- Prefer real objects/fixtures over mocks; mock only true external boundaries (network, filesystem, clock, randomness). +- Tests must be deterministic — no reliance on wall-clock time, network access, or test execution order. +- A failing test's assertion message should make the failure diagnosable without opening a debugger. diff --git a/.agent/rules/typescript_react.md b/.agent/rules/typescript_react.md new file mode 100644 index 0000000..9f5111a --- /dev/null +++ b/.agent/rules/typescript_react.md @@ -0,0 +1,9 @@ +# TypeScript / React Rules + +- Target TypeScript 5, `strict: true` in `tsconfig.json`. No `any` without a `// TODO` explaining why. +- Format/lint with `eslint` (`eslint-config-next`); run `npm run lint` before committing. +- Prefer function components with hooks. +- State management: local `useState`/`useReducer` first; this site has no shared/global store today — don't add one for a single component's state. +- Unit tests live under `test/unit/`, mirroring `src/components/`'s layout, using Vitest + Testing Library. Snapshot tests are a last resort, not a default. Tests spanning more than one component (e.g. anything through `ClientLayoutWrapper`) belong in `test/integration/` instead, mocking any network calls with MSW. +- Remember this is a static export (`output: 'export'`): no API routes, no server components that need a runtime, no browser-only API calls outside `useEffect`/client components. +- Content is Markdown parsed at build time via `lib/markdown.ts`; new content sections belong under `app/content/
/`, not hardcoded in a component. diff --git a/.agent/skills/build-and-test.md b/.agent/skills/build-and-test.md new file mode 100644 index 0000000..6dd8a06 --- /dev/null +++ b/.agent/skills/build-and-test.md @@ -0,0 +1,23 @@ +# Skill: Build and Test Everything + +Run the full build + test cycle for the site. + +```bash +npm run lint # eslint +npx tsc --noEmit # type check +npm test # vitest: unit (test/unit/) + integration (test/integration/) +npm run build # static export to out/ +npm run cypress:run # e2e + smoke (test/cypress/), against a running dev/build server +``` + +For the notebooks workspace: + +```bash +cd notebooks +uv sync --extra dev +uv run ruff check . +uv run mypy . # advisory +``` + +Use this before opening a PR, or whenever asked to "make sure everything still works." +Report which step failed and the first failing assertion/error, not just "tests failed." diff --git a/.agent/skills/debug-crash.md b/.agent/skills/debug-crash.md new file mode 100644 index 0000000..54bd07c --- /dev/null +++ b/.agent/skills/debug-crash.md @@ -0,0 +1,7 @@ +# Skill: Debug a Build or Runtime Failure + +1. Get a reliable repro: the exact command (`npm run dev`/`npm run build`/`npm test`/`npm run cypress:run`) and the full error output. +2. Capture the full stack trace/browser console output — do not summarize it. +3. For a build-only failure, check first whether it's a static-export constraint (no API routes, no server-only Node APIs in client components, no `window`/`document` access outside `useEffect`). +4. Bisect via `git bisect` if the failure is a regression against a known-good commit. +5. Once fixed, add a regression test (Vitest for components/logic, Cypress for a broken user flow) and note the root cause in the commit message. diff --git a/.agent/workflows/code_refactor.md b/.agent/workflows/code_refactor.md new file mode 100644 index 0000000..f5113e2 --- /dev/null +++ b/.agent/workflows/code_refactor.md @@ -0,0 +1,7 @@ +# Workflow: Refactoring + +1. Confirm test coverage exists for the code being refactored; add characterization tests first if it doesn't. +2. Make one mechanical change at a time (rename, extract function, move file) and re-run tests after each. +3. Never mix a refactor with a behavior change — split into separate commits if both are needed. +4. Update all call sites and documentation references in the same change as the rename/move. +5. Delete now-dead code rather than commenting it out. diff --git a/.agent/workflows/code_review.md b/.agent/workflows/code_review.md new file mode 100644 index 0000000..f688dc0 --- /dev/null +++ b/.agent/workflows/code_review.md @@ -0,0 +1,8 @@ +# Workflow: Code Review + +1. Read the diff in full before commenting — don't review file-by-file in isolation when a change spans multiple files. +2. Run the test suite and linters locally (or confirm CI is green) before starting a substantive review. +3. Check the change against `.agent/rules/code_review.md` and the relevant language rule file. +4. Leave findings ranked by severity: correctness/security first, then design, then style/nits. +5. For each finding, state the concrete failure scenario (input → wrong output), not just "this looks wrong." +6. Approve once blocking issues are resolved; don't hold a PR hostage over nits. diff --git a/.agent/workflows/error_debug.md b/.agent/workflows/error_debug.md new file mode 100644 index 0000000..fad1d76 --- /dev/null +++ b/.agent/workflows/error_debug.md @@ -0,0 +1,8 @@ +# Workflow: Debugging an Error + +1. Reproduce the failure deterministically — capture the exact input, command, and environment. +2. Read the full stack trace/log before forming a hypothesis; don't guess from the last line alone. +3. Bisect: comment out / isolate halves of the suspect code path until the failure localizes to a small region. +4. Write a minimal failing test that reproduces the bug in isolation. +5. Fix the root cause, confirm the new test passes, then run the broader suite for regressions. +6. Document the root cause and fix in the commit message — not just "fixed bug." diff --git a/.agent/workflows/gui_dev.md b/.agent/workflows/gui_dev.md new file mode 100644 index 0000000..b006817 --- /dev/null +++ b/.agent/workflows/gui_dev.md @@ -0,0 +1,7 @@ +# Workflow: GUI Feature + +1. Sketch the component tree and identify what state lives where (local vs. shared/store). +2. Build the presentational component first with mock data/props, verify it renders correctly. +3. Wire it to real data/state; handle loading, empty, and error states explicitly. +4. Add keyboard navigation and accessible labels. +5. Add a component/integration test, then manually exercise the golden path and at least one edge case in a running instance. diff --git a/.agent/workflows/reasoning_planning.md b/.agent/workflows/reasoning_planning.md new file mode 100644 index 0000000..c629313 --- /dev/null +++ b/.agent/workflows/reasoning_planning.md @@ -0,0 +1,7 @@ +# Workflow: Planning a Non-Trivial Task + +1. Restate the goal in one sentence and identify the acceptance criteria. +2. List the files/modules likely to change and any that are risky to touch. +3. Break the work into independently testable steps; identify which steps can run in parallel. +4. Flag ambiguities and decisions that belong to the user rather than guessing. +5. Execute step by step, checking off progress and adjusting the plan when new information appears. diff --git a/.agent/workflows/test_writing.md b/.agent/workflows/test_writing.md new file mode 100644 index 0000000..cbe6f62 --- /dev/null +++ b/.agent/workflows/test_writing.md @@ -0,0 +1,7 @@ +# Workflow: Writing Tests + +1. Identify the unit under test and its public contract (inputs, outputs, side effects, error modes). +2. Write the happy-path test first, then edge cases (empty input, boundary values, max size), then failure cases. +3. Run the new test and confirm it fails for the right reason before implementing the fix/feature. +4. Implement, then re-run until green. Run the full module's test suite to check for regressions. +5. Check coverage of the new/changed lines; add tests for any branch left uncovered that matters. diff --git a/.agent/workflows/typescript_react.md b/.agent/workflows/typescript_react.md new file mode 100644 index 0000000..9849fef --- /dev/null +++ b/.agent/workflows/typescript_react.md @@ -0,0 +1,7 @@ +# Workflow: TypeScript/React Feature + +1. Define the component's props/types first; let TypeScript surface integration issues before runtime. +2. Build and test the component in isolation (e.g. via a story or a standalone test) before wiring it into a page. +3. Run `tsc --noEmit` and the linter before committing. +4. Add a Vitest + Testing Library test covering render, interaction, and at least one error/empty state. +5. Verify in a running dev server, not just in tests. diff --git a/.astro/content.d.ts b/.astro/content.d.ts new file mode 100644 index 0000000..2bf13df --- /dev/null +++ b/.astro/content.d.ts @@ -0,0 +1,159 @@ +declare module 'astro:content' { + export interface RenderResult { + Content: import('astro/runtime/server/index.js').AstroComponentFactory; + headings: import('astro').MarkdownHeading[]; + remarkPluginFrontmatter: Record; + } + interface Render { + '.md': Promise; + } + + export interface RenderedContent { + html: string; + metadata?: { + imagePaths: Array; + [key: string]: unknown; + }; + } + + type Flatten = T extends { [K: string]: infer U } ? U : never; + + export type CollectionKey = keyof DataEntryMap; + export type CollectionEntry = Flatten; + + type AllValuesOf = T extends any ? T[keyof T] : never; + + export type ReferenceDataEntry< + C extends CollectionKey, + E extends keyof DataEntryMap[C] = string, + > = { + collection: C; + id: E; + }; + + export type ReferenceLiveEntry = { + collection: C; + id: string; + }; + + export function getCollection>( + collection: C, + filter?: (entry: CollectionEntry) => entry is E, + ): Promise; + export function getCollection( + collection: C, + filter?: (entry: CollectionEntry) => unknown, + ): Promise[]>; + + export function getLiveCollection( + collection: C, + filter?: LiveLoaderCollectionFilterType, + ): Promise< + import('astro').LiveDataCollectionResult, LiveLoaderErrorType> + >; + + export function getEntry< + C extends keyof DataEntryMap, + E extends keyof DataEntryMap[C] | (string & {}), + >( + entry: ReferenceDataEntry, + ): E extends keyof DataEntryMap[C] + ? Promise + : Promise | undefined>; + export function getEntry< + C extends keyof DataEntryMap, + E extends keyof DataEntryMap[C] | (string & {}), + >( + collection: C, + id: E, + ): E extends keyof DataEntryMap[C] + ? string extends keyof DataEntryMap[C] + ? Promise | undefined + : Promise + : Promise | undefined>; + export function getLiveEntry( + collection: C, + filter: string | LiveLoaderEntryFilterType, + ): Promise, LiveLoaderErrorType>>; + + /** Resolve an array of entry references from the same collection */ + export function getEntries( + entries: ReferenceDataEntry[], + ): Promise[]>; + + export function render( + entry: DataEntryMap[C][string], + ): Promise; + + export function render( + entry: import('astro').LiveDataEntry>, + ): Promise; + + export function reference< + C extends + | keyof DataEntryMap + // Allow generic `string` to avoid excessive type errors in the config + // if `dev` is not running to update as you edit. + // Invalid collection names will be caught at build time. + | (string & {}), + >( + collection: C, + ): import('astro/zod').ZodPipe< + import('astro/zod').ZodString, + import('astro/zod').ZodTransform< + C extends keyof DataEntryMap + ? { + collection: C; + id: string; + } + : never, + string + > + >; + + type ReturnTypeOrOriginal = T extends (...args: any[]) => infer R ? R : T; + type InferEntrySchema = import('astro/zod').infer< + ReturnTypeOrOriginal['schema']> + >; + type ExtractLoaderConfig = T extends { loader: infer L } ? L : never; + type InferLoaderSchema< + C extends keyof DataEntryMap, + L = ExtractLoaderConfig, + > = L extends { schema: import('astro/zod').ZodSchema } + ? import('astro/zod').infer + : any; + + type DataEntryMap = { + + }; + + type ExtractLoaderTypes = T extends import('astro/loaders').LiveLoader< + infer TData, + infer TEntryFilter, + infer TCollectionFilter, + infer TError + > + ? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError } + : { data: never; entryFilter: never; collectionFilter: never; error: never }; + type ExtractEntryFilterType = ExtractLoaderTypes['entryFilter']; + type ExtractCollectionFilterType = ExtractLoaderTypes['collectionFilter']; + type ExtractErrorType = ExtractLoaderTypes['error']; + type ExtractDataType = ExtractLoaderTypes['data']; + + type LiveLoaderDataType = + LiveContentConfig['collections'][C]['schema'] extends undefined + ? ExtractDataType + : import('astro/zod').infer< + Exclude + >; + type LiveLoaderEntryFilterType = + ExtractEntryFilterType; + type LiveLoaderCollectionFilterType = + ExtractCollectionFilterType; + type LiveLoaderErrorType = ExtractErrorType< + LiveContentConfig['collections'][C]['loader'] + >; + + export type ContentConfig = never; + export type LiveContentConfig = never; +} diff --git a/.astro/types.d.ts b/.astro/types.d.ts new file mode 100644 index 0000000..03d7cc4 --- /dev/null +++ b/.astro/types.d.ts @@ -0,0 +1,2 @@ +/// +/// \ No newline at end of file diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..c2c265e --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,21 @@ +# Dev Container image: Node.js for the Next.js site plus a Python +# toolchain for the notebooks/ research workspace. +FROM node:20-slim + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y \ + curl \ + git \ + python3 \ + && rm -rf /var/lib/apt/lists/* + +# uv for fast Python package management (notebooks/) +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ + +# Create a non-root user for development +RUN useradd -m -s /bin/bash vscode +USER vscode +WORKDIR /app + +CMD ["/bin/bash"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..fd7c08c --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,29 @@ +{ + "name": "github-pages Dev Environment", + "build": { + "dockerfile": "Dockerfile", + "context": ".." + }, + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "bradlc.vscode-tailwindcss", + "ms-python.python", + "charliermarsh.ruff" + ], + "settings": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff" + } + } + } + }, + "postCreateCommand": "npm ci && (cd notebooks && uv sync --extra dev) && pre-commit install", + "remoteUser": "vscode" +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9ccc3e0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,28 @@ +# Keep the dev container build context small and reproducible. + +# VCS / CI +.git +.github +.gitignore + +# Node / Next.js +node_modules +.next +out + +# Python (notebooks/) +.venv +**/__pycache__ +*.py[cod] +.mypy_cache +.ruff_cache +.pytest_cache +*.egg-info + +# Editors / OS +.vscode +.idea +.DS_Store + +# Secrets +.env diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..49ab087 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,2 @@ +/** Re-export project ESLint config from stack/eslint/. */ +module.exports = require('./stack/eslint/.eslintrc.json'); diff --git a/.forgejo/ISSUE_TEMPLATE/bug_report.yml b/.forgejo/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..ed71da4 --- /dev/null +++ b/.forgejo/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,49 @@ +name: 🐛 Bug Report +description: Report a defect (structured for humans and coding agents) +title: "[Bug]: " +labels: ["bug", "triage"] +body: + - type: markdown + attributes: + value: | + Thanks for the report. Telling us which page or area is affected speeds up triage. + + - type: dropdown + id: area + attributes: + label: Affected area + description: Which part of the site? + options: + - Posts + - Reports + - Projects + - Tools + - Media + - About / Other + - Notebooks (notebooks/) + - Build / tooling / CI + - Other / unknown + validations: + required: true + + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Expected vs actual behavior. + validations: + required: true + + - type: textarea + id: repro + attributes: + label: Steps to reproduce + description: Minimal, exact steps — commands, inputs, environment. + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Relevant log output + render: shell diff --git a/.forgejo/ISSUE_TEMPLATE/config.yml b/.forgejo/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..464e151 --- /dev/null +++ b/.forgejo/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: 🌐 Live Site + url: https://acfharbinger.github.io/github-pages/ + about: The deployed site this repository builds. + - name: 📖 Docs + url: https://github.com/ACFHarbinger/github-pages/tree/main/docs + about: Research notes and write-ups backing the site's reports/posts. diff --git a/.forgejo/ISSUE_TEMPLATE/feature_request.yml b/.forgejo/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..bf3b70e --- /dev/null +++ b/.forgejo/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,35 @@ +name: ✨ Feature Request +description: Propose a new feature or enhancement +title: "[Feature]: " +labels: ["enhancement", "triage"] +body: + - type: textarea + id: problem + attributes: + label: What problem does this solve? + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed solution + validations: + required: false + + - type: dropdown + id: area + attributes: + label: Affected area + options: + - Posts + - Reports + - Projects + - Tools + - Media + - About / Other + - Notebooks (notebooks/) + - Build / tooling / CI + - Other / unknown + validations: + required: true diff --git a/.forgejo/pull_request_template.md b/.forgejo/pull_request_template.md new file mode 100644 index 0000000..a68fef2 --- /dev/null +++ b/.forgejo/pull_request_template.md @@ -0,0 +1,26 @@ +# Pull Request + +## Summary + + + +## Affected Area(s) + +- [ ] Site content (`app/content/**`) +- [ ] Components / styling (`src/`) +- [ ] Notebooks / research (`notebooks/`, `docs/moon/research/`, `docs/moon/reports/`) +- [ ] Tooling / CI / docs + +## Type of Change + +- [ ] 🐛 Bug fix +- [ ] ✨ New feature +- [ ] ♻️ Refactor +- [ ] 📚 Content / documentation +- [ ] 🔧 Tooling / CI + +## Verification + +- [ ] `npm run lint` and `npm test` pass. +- [ ] `npm run build` succeeds (static export). +- [ ] Manually checked the affected page(s) with `npm run dev`. diff --git a/.forgejo/workflows/agent-sync.yml b/.forgejo/workflows/agent-sync.yml new file mode 100644 index 0000000..2bad582 --- /dev/null +++ b/.forgejo/workflows/agent-sync.yml @@ -0,0 +1,72 @@ +name: Agent Backlog Sync + +# Keeps a GitHub Project (V2) board in sync with docs/moon/ROADMAP.md / +# docs/moon/CHANGELOG.md by orchestrating git/scripts/sync_backlog.py. Runs on +# pushes to main that touch either source-of-truth doc, or on demand via +# workflow_dispatch for maintainers who want to force a resync without a doc +# edit. + +on: + push: + branches: [main] + paths: + - "docs/moon/ROADMAP.md" + - "docs/moon/CHANGELOG.md" + workflow_dispatch: + inputs: + dry_run: + description: "Print the proposed diff without mutating the board" + type: boolean + default: false + +permissions: + contents: read + issues: write + pull-requests: write + +concurrency: + group: agent-backlog-sync + cancel-in-progress: false + +jobs: + sync: + name: Sync backlog to Project board + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - uses: astral-sh/setup-uv@v5 + + - name: Install automation dependencies + run: uv sync + working-directory: git + + - name: Validate automation config + run: | + uv run --project git python -c "import json,pathlib; json.loads(pathlib.Path('git/config/project_labels.json').read_text())" + uv run --project git python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('git/config/automation_rules.yaml').read_text())" + + - name: Run backlog sync + env: + GITHUB_TOKEN: ${{ secrets.PROJECT_AUTOMATION_TOKEN }} + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + PROJECT_ID: ${{ vars.PROJECT_ID }} + run: | + ARGS="--repo-owner ${{ github.repository_owner }} \ + --repo-name $(basename ${{ github.repository }}) \ + --project-id ${PROJECT_ID}" + if [ "${{ inputs.dry_run }}" = "true" ]; then + ARGS="$ARGS --dry-run" + fi + uv run --project git python -m git.scripts.sync_backlog $ARGS + + - name: Summarize run + if: always() + run: | + echo "### Agent Backlog Sync" >> "$GITHUB_STEP_SUMMARY" + echo "Trigger: ${{ github.event_name }}" >> "$GITHUB_STEP_SUMMARY" + echo "Dry run: ${{ inputs.dry_run || 'false' }}" >> "$GITHUB_STEP_SUMMARY" diff --git a/.forgejo/workflows/benchmark.yml b/.forgejo/workflows/benchmark.yml new file mode 100644 index 0000000..8ed5c2a --- /dev/null +++ b/.forgejo/workflows/benchmark.yml @@ -0,0 +1,32 @@ +name: Benchmark + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "app/**" + - "src/**" + - "public/**" + +jobs: + lighthouse: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + - name: Serve static export + run: npx serve@latest out -l 3000 & + - name: Wait for server + run: npx wait-on http://localhost:3000/github-pages + - name: Run Lighthouse + run: npx lighthouse http://localhost:3000/github-pages --output=json --output-path=./lighthouse-report.json --chrome-flags="--headless --no-sandbox" + - uses: actions/upload-artifact@v4 + with: + name: lighthouse-report + path: lighthouse-report.json diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml new file mode 100644 index 0000000..84129ea --- /dev/null +++ b/.forgejo/workflows/ci.yml @@ -0,0 +1,78 @@ +name: CI + +on: + workflow_dispatch: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint-typecheck-unit: + name: Lint, typecheck & unit tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run lint + - run: npx tsc --noEmit + - run: npm run test:unit + - run: npm run test:integration + + build: + name: Build static export + runs-on: ubuntu-latest + needs: lint-typecheck-unit + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + - uses: actions/upload-artifact@v4 + with: + name: site-export + path: out/ + retention-days: 3 + + e2e: + name: Cypress e2e & smoke + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + - name: Run Cypress against the static export + uses: cypress-io/github-action@v6 + with: + working-directory: test/cypress + start: npm start --prefix ../.. + wait-on: "http://localhost:3000/github-pages" + # cypress.config.js's specPattern already covers both e2e/ and + # smoke/, so this one run covers both suites. + + notebooks: + name: Lint notebooks workspace + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --extra dev + working-directory: notebooks + - run: uv run ruff check . + working-directory: notebooks diff --git a/.forgejo/workflows/docs.yml b/.forgejo/workflows/docs.yml new file mode 100644 index 0000000..7d747fd --- /dev/null +++ b/.forgejo/workflows/docs.yml @@ -0,0 +1,18 @@ +name: Docs + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "docs/**" + +jobs: + link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: lycheeverse/lychee-action@v1 + with: + args: --verbose --no-progress --include-verbatim 'docs/**/*.md' + fail: true diff --git a/.forgejo/workflows/security.yml b/.forgejo/workflows/security.yml new file mode 100644 index 0000000..fcd4bec --- /dev/null +++ b/.forgejo/workflows/security.yml @@ -0,0 +1,28 @@ +name: Security Audit + +on: + workflow_dispatch: + schedule: + - cron: "0 6 * * 1" # every Monday 06:00 UTC + pull_request: + branches: [main] + +jobs: + npm-audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: npm audit --audit-level=high + + pip-audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --extra dev + working-directory: notebooks + - run: uv run pip-audit + working-directory: notebooks diff --git a/.gitea/ISSUE_TEMPLATE/bug_report.yml b/.gitea/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..ed71da4 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,49 @@ +name: 🐛 Bug Report +description: Report a defect (structured for humans and coding agents) +title: "[Bug]: " +labels: ["bug", "triage"] +body: + - type: markdown + attributes: + value: | + Thanks for the report. Telling us which page or area is affected speeds up triage. + + - type: dropdown + id: area + attributes: + label: Affected area + description: Which part of the site? + options: + - Posts + - Reports + - Projects + - Tools + - Media + - About / Other + - Notebooks (notebooks/) + - Build / tooling / CI + - Other / unknown + validations: + required: true + + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Expected vs actual behavior. + validations: + required: true + + - type: textarea + id: repro + attributes: + label: Steps to reproduce + description: Minimal, exact steps — commands, inputs, environment. + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Relevant log output + render: shell diff --git a/.gitea/ISSUE_TEMPLATE/config.yml b/.gitea/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..464e151 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: 🌐 Live Site + url: https://acfharbinger.github.io/github-pages/ + about: The deployed site this repository builds. + - name: 📖 Docs + url: https://github.com/ACFHarbinger/github-pages/tree/main/docs + about: Research notes and write-ups backing the site's reports/posts. diff --git a/.gitea/ISSUE_TEMPLATE/feature_request.yml b/.gitea/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..bf3b70e --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,35 @@ +name: ✨ Feature Request +description: Propose a new feature or enhancement +title: "[Feature]: " +labels: ["enhancement", "triage"] +body: + - type: textarea + id: problem + attributes: + label: What problem does this solve? + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed solution + validations: + required: false + + - type: dropdown + id: area + attributes: + label: Affected area + options: + - Posts + - Reports + - Projects + - Tools + - Media + - About / Other + - Notebooks (notebooks/) + - Build / tooling / CI + - Other / unknown + validations: + required: true diff --git a/.gitea/pull_request_template.md b/.gitea/pull_request_template.md new file mode 100644 index 0000000..a68fef2 --- /dev/null +++ b/.gitea/pull_request_template.md @@ -0,0 +1,26 @@ +# Pull Request + +## Summary + + + +## Affected Area(s) + +- [ ] Site content (`app/content/**`) +- [ ] Components / styling (`src/`) +- [ ] Notebooks / research (`notebooks/`, `docs/moon/research/`, `docs/moon/reports/`) +- [ ] Tooling / CI / docs + +## Type of Change + +- [ ] 🐛 Bug fix +- [ ] ✨ New feature +- [ ] ♻️ Refactor +- [ ] 📚 Content / documentation +- [ ] 🔧 Tooling / CI + +## Verification + +- [ ] `npm run lint` and `npm test` pass. +- [ ] `npm run build` succeeds (static export). +- [ ] Manually checked the affected page(s) with `npm run dev`. diff --git a/.gitea/workflows/agent-sync.yml b/.gitea/workflows/agent-sync.yml new file mode 100644 index 0000000..2bad582 --- /dev/null +++ b/.gitea/workflows/agent-sync.yml @@ -0,0 +1,72 @@ +name: Agent Backlog Sync + +# Keeps a GitHub Project (V2) board in sync with docs/moon/ROADMAP.md / +# docs/moon/CHANGELOG.md by orchestrating git/scripts/sync_backlog.py. Runs on +# pushes to main that touch either source-of-truth doc, or on demand via +# workflow_dispatch for maintainers who want to force a resync without a doc +# edit. + +on: + push: + branches: [main] + paths: + - "docs/moon/ROADMAP.md" + - "docs/moon/CHANGELOG.md" + workflow_dispatch: + inputs: + dry_run: + description: "Print the proposed diff without mutating the board" + type: boolean + default: false + +permissions: + contents: read + issues: write + pull-requests: write + +concurrency: + group: agent-backlog-sync + cancel-in-progress: false + +jobs: + sync: + name: Sync backlog to Project board + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - uses: astral-sh/setup-uv@v5 + + - name: Install automation dependencies + run: uv sync + working-directory: git + + - name: Validate automation config + run: | + uv run --project git python -c "import json,pathlib; json.loads(pathlib.Path('git/config/project_labels.json').read_text())" + uv run --project git python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('git/config/automation_rules.yaml').read_text())" + + - name: Run backlog sync + env: + GITHUB_TOKEN: ${{ secrets.PROJECT_AUTOMATION_TOKEN }} + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + PROJECT_ID: ${{ vars.PROJECT_ID }} + run: | + ARGS="--repo-owner ${{ github.repository_owner }} \ + --repo-name $(basename ${{ github.repository }}) \ + --project-id ${PROJECT_ID}" + if [ "${{ inputs.dry_run }}" = "true" ]; then + ARGS="$ARGS --dry-run" + fi + uv run --project git python -m git.scripts.sync_backlog $ARGS + + - name: Summarize run + if: always() + run: | + echo "### Agent Backlog Sync" >> "$GITHUB_STEP_SUMMARY" + echo "Trigger: ${{ github.event_name }}" >> "$GITHUB_STEP_SUMMARY" + echo "Dry run: ${{ inputs.dry_run || 'false' }}" >> "$GITHUB_STEP_SUMMARY" diff --git a/.gitea/workflows/benchmark.yml b/.gitea/workflows/benchmark.yml new file mode 100644 index 0000000..8ed5c2a --- /dev/null +++ b/.gitea/workflows/benchmark.yml @@ -0,0 +1,32 @@ +name: Benchmark + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "app/**" + - "src/**" + - "public/**" + +jobs: + lighthouse: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + - name: Serve static export + run: npx serve@latest out -l 3000 & + - name: Wait for server + run: npx wait-on http://localhost:3000/github-pages + - name: Run Lighthouse + run: npx lighthouse http://localhost:3000/github-pages --output=json --output-path=./lighthouse-report.json --chrome-flags="--headless --no-sandbox" + - uses: actions/upload-artifact@v4 + with: + name: lighthouse-report + path: lighthouse-report.json diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..84129ea --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,78 @@ +name: CI + +on: + workflow_dispatch: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint-typecheck-unit: + name: Lint, typecheck & unit tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run lint + - run: npx tsc --noEmit + - run: npm run test:unit + - run: npm run test:integration + + build: + name: Build static export + runs-on: ubuntu-latest + needs: lint-typecheck-unit + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + - uses: actions/upload-artifact@v4 + with: + name: site-export + path: out/ + retention-days: 3 + + e2e: + name: Cypress e2e & smoke + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + - name: Run Cypress against the static export + uses: cypress-io/github-action@v6 + with: + working-directory: test/cypress + start: npm start --prefix ../.. + wait-on: "http://localhost:3000/github-pages" + # cypress.config.js's specPattern already covers both e2e/ and + # smoke/, so this one run covers both suites. + + notebooks: + name: Lint notebooks workspace + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --extra dev + working-directory: notebooks + - run: uv run ruff check . + working-directory: notebooks diff --git a/.gitea/workflows/docs.yml b/.gitea/workflows/docs.yml new file mode 100644 index 0000000..7d747fd --- /dev/null +++ b/.gitea/workflows/docs.yml @@ -0,0 +1,18 @@ +name: Docs + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "docs/**" + +jobs: + link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: lycheeverse/lychee-action@v1 + with: + args: --verbose --no-progress --include-verbatim 'docs/**/*.md' + fail: true diff --git a/.gitea/workflows/security.yml b/.gitea/workflows/security.yml new file mode 100644 index 0000000..fcd4bec --- /dev/null +++ b/.gitea/workflows/security.yml @@ -0,0 +1,28 @@ +name: Security Audit + +on: + workflow_dispatch: + schedule: + - cron: "0 6 * * 1" # every Monday 06:00 UTC + pull_request: + branches: [main] + +jobs: + npm-audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: npm audit --audit-level=high + + pip-audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --extra dev + working-directory: notebooks + - run: uv run pip-audit + working-directory: notebooks diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..ed71da4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,49 @@ +name: 🐛 Bug Report +description: Report a defect (structured for humans and coding agents) +title: "[Bug]: " +labels: ["bug", "triage"] +body: + - type: markdown + attributes: + value: | + Thanks for the report. Telling us which page or area is affected speeds up triage. + + - type: dropdown + id: area + attributes: + label: Affected area + description: Which part of the site? + options: + - Posts + - Reports + - Projects + - Tools + - Media + - About / Other + - Notebooks (notebooks/) + - Build / tooling / CI + - Other / unknown + validations: + required: true + + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Expected vs actual behavior. + validations: + required: true + + - type: textarea + id: repro + attributes: + label: Steps to reproduce + description: Minimal, exact steps — commands, inputs, environment. + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Relevant log output + render: shell diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..464e151 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: 🌐 Live Site + url: https://acfharbinger.github.io/github-pages/ + about: The deployed site this repository builds. + - name: 📖 Docs + url: https://github.com/ACFHarbinger/github-pages/tree/main/docs + about: Research notes and write-ups backing the site's reports/posts. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..bf3b70e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,35 @@ +name: ✨ Feature Request +description: Propose a new feature or enhancement +title: "[Feature]: " +labels: ["enhancement", "triage"] +body: + - type: textarea + id: problem + attributes: + label: What problem does this solve? + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed solution + validations: + required: false + + - type: dropdown + id: area + attributes: + label: Affected area + options: + - Posts + - Reports + - Projects + - Tools + - Media + - About / Other + - Notebooks (notebooks/) + - Build / tooling / CI + - Other / unknown + validations: + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..a68fef2 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,26 @@ +# Pull Request + +## Summary + + + +## Affected Area(s) + +- [ ] Site content (`app/content/**`) +- [ ] Components / styling (`src/`) +- [ ] Notebooks / research (`notebooks/`, `docs/moon/research/`, `docs/moon/reports/`) +- [ ] Tooling / CI / docs + +## Type of Change + +- [ ] 🐛 Bug fix +- [ ] ✨ New feature +- [ ] ♻️ Refactor +- [ ] 📚 Content / documentation +- [ ] 🔧 Tooling / CI + +## Verification + +- [ ] `npm run lint` and `npm test` pass. +- [ ] `npm run build` succeeds (static export). +- [ ] Manually checked the affected page(s) with `npm run dev`. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8ac6b8c..2e13794 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,6 +1,19 @@ version: 2 updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + groups: + npm-dependencies: + patterns: ["*"] + + - package-ecosystem: "uv" + directory: "/notebooks" + schedule: + interval: "weekly" + - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "monthly" + interval: "weekly" diff --git a/.github/workflows/agent_sync.yml b/.github/workflows/agent_sync.yml new file mode 100644 index 0000000..2bad582 --- /dev/null +++ b/.github/workflows/agent_sync.yml @@ -0,0 +1,72 @@ +name: Agent Backlog Sync + +# Keeps a GitHub Project (V2) board in sync with docs/moon/ROADMAP.md / +# docs/moon/CHANGELOG.md by orchestrating git/scripts/sync_backlog.py. Runs on +# pushes to main that touch either source-of-truth doc, or on demand via +# workflow_dispatch for maintainers who want to force a resync without a doc +# edit. + +on: + push: + branches: [main] + paths: + - "docs/moon/ROADMAP.md" + - "docs/moon/CHANGELOG.md" + workflow_dispatch: + inputs: + dry_run: + description: "Print the proposed diff without mutating the board" + type: boolean + default: false + +permissions: + contents: read + issues: write + pull-requests: write + +concurrency: + group: agent-backlog-sync + cancel-in-progress: false + +jobs: + sync: + name: Sync backlog to Project board + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - uses: astral-sh/setup-uv@v5 + + - name: Install automation dependencies + run: uv sync + working-directory: git + + - name: Validate automation config + run: | + uv run --project git python -c "import json,pathlib; json.loads(pathlib.Path('git/config/project_labels.json').read_text())" + uv run --project git python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('git/config/automation_rules.yaml').read_text())" + + - name: Run backlog sync + env: + GITHUB_TOKEN: ${{ secrets.PROJECT_AUTOMATION_TOKEN }} + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + PROJECT_ID: ${{ vars.PROJECT_ID }} + run: | + ARGS="--repo-owner ${{ github.repository_owner }} \ + --repo-name $(basename ${{ github.repository }}) \ + --project-id ${PROJECT_ID}" + if [ "${{ inputs.dry_run }}" = "true" ]; then + ARGS="$ARGS --dry-run" + fi + uv run --project git python -m git.scripts.sync_backlog $ARGS + + - name: Summarize run + if: always() + run: | + echo "### Agent Backlog Sync" >> "$GITHUB_STEP_SUMMARY" + echo "Trigger: ${{ github.event_name }}" >> "$GITHUB_STEP_SUMMARY" + echo "Dry run: ${{ inputs.dry_run || 'false' }}" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 0000000..8ed5c2a --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,32 @@ +name: Benchmark + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "app/**" + - "src/**" + - "public/**" + +jobs: + lighthouse: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + - name: Serve static export + run: npx serve@latest out -l 3000 & + - name: Wait for server + run: npx wait-on http://localhost:3000/github-pages + - name: Run Lighthouse + run: npx lighthouse http://localhost:3000/github-pages --output=json --output-path=./lighthouse-report.json --chrome-flags="--headless --no-sandbox" + - uses: actions/upload-artifact@v4 + with: + name: lighthouse-report + path: lighthouse-report.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..84129ea --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,78 @@ +name: CI + +on: + workflow_dispatch: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint-typecheck-unit: + name: Lint, typecheck & unit tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run lint + - run: npx tsc --noEmit + - run: npm run test:unit + - run: npm run test:integration + + build: + name: Build static export + runs-on: ubuntu-latest + needs: lint-typecheck-unit + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + - uses: actions/upload-artifact@v4 + with: + name: site-export + path: out/ + retention-days: 3 + + e2e: + name: Cypress e2e & smoke + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npm run build + - name: Run Cypress against the static export + uses: cypress-io/github-action@v6 + with: + working-directory: test/cypress + start: npm start --prefix ../.. + wait-on: "http://localhost:3000/github-pages" + # cypress.config.js's specPattern already covers both e2e/ and + # smoke/, so this one run covers both suites. + + notebooks: + name: Lint notebooks workspace + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --extra dev + working-directory: notebooks + - run: uv run ruff check . + working-directory: notebooks diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..7d747fd --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,18 @@ +name: Docs + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "docs/**" + +jobs: + link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: lycheeverse/lychee-action@v1 + with: + args: --verbose --no-progress --include-verbatim 'docs/**/*.md' + fail: true diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..fcd4bec --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,28 @@ +name: Security Audit + +on: + workflow_dispatch: + schedule: + - cron: "0 6 * * 1" # every Monday 06:00 UTC + pull_request: + branches: [main] + +jobs: + npm-audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: npm audit --audit-level=high + + pip-audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - run: uv sync --extra dev + working-directory: notebooks + - run: uv run pip-audit + working-directory: notebooks diff --git a/.gitignore b/.gitignore index 09944f5..edf3242 100644 --- a/.gitignore +++ b/.gitignore @@ -61,10 +61,17 @@ vendor/ ########################### .next out +*.tsbuildinfo + +# Cypress # +########################### +test/cypress/screenshots +test/cypress/videos # Other # ######### tmp .cache +benchmark/results .vscode -/**/gatsby-types.d.ts \ No newline at end of file +/**/gatsby-types.d.ts diff --git a/.gitlab/.gitlab-ci.yml b/.gitlab/.gitlab-ci.yml new file mode 100644 index 0000000..03bb6b7 --- /dev/null +++ b/.gitlab/.gitlab-ci.yml @@ -0,0 +1,132 @@ +stages: + - test + - security + - docs + - benchmark + - sync + +# ========================================== +# STAGE: TEST (ci.yml) +# ========================================== +lint-typecheck-unit: + stage: test + image: node:20 + script: + - npm ci + - npm run lint + - npx tsc --noEmit + - npm run test:unit + - npm run test:integration + +build: + stage: test + image: node:20 + needs: ["lint-typecheck-unit"] + script: + - npm ci + - npm run build + artifacts: + paths: + - out + +e2e: + stage: test + image: cypress/browsers:node-20.11.1-chrome-123.0.6312.86-1-ff-124.0-edge-122.0.2365.92-1 + needs: ["build"] + script: + - npm ci + - npm run build + # cypress.config.js's specPattern covers both e2e/ and smoke/, so this + # one run covers both suites. + - npx start-server-and-test start http://localhost:3000/github-pages "npm run cypress:run" + +lint-notebooks: + stage: test + image: python:3.12 + script: + - pip install uv + - cd notebooks + - uv sync --extra dev + - uv run ruff check . + +# ========================================== +# STAGE: SECURITY (security.yml) +# ========================================== +security-npm-audit: + stage: security + image: node:20 + rules: + - if: '$CI_PIPELINE_SOURCE == "schedule"' + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + script: + - npm audit --audit-level=high + +security-pip-audit: + stage: security + image: python:3.12 + rules: + - if: '$CI_PIPELINE_SOURCE == "schedule"' + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + script: + - pip install uv + - cd notebooks + - uv sync --extra dev + - uv run pip-audit + +# ========================================== +# STAGE: DOCS (docs.yml) +# ========================================== +docs-link-check: + stage: docs + image: lycheeverse/lychee:latest + rules: + - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' + changes: + - "docs/**/*" + script: + - lychee --verbose --no-progress --include-verbatim 'docs/**/*.md' + +# ========================================== +# STAGE: BENCHMARK (benchmark.yml) +# ========================================== +lighthouse: + stage: benchmark + image: node:20 + rules: + - if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH" + changes: + - "app/**/*" + - "src/**/*" + - "public/**/*" + - if: '$CI_PIPELINE_SOURCE == "web"' + script: + - npm ci + - npm run build + - npx serve@latest out -l 3000 & + - npx wait-on http://localhost:3000/github-pages + - npx lighthouse http://localhost:3000/github-pages --output=json --output-path=./lighthouse-report.json --chrome-flags="--headless --no-sandbox" + artifacts: + paths: + - lighthouse-report.json + +# ========================================== +# STAGE: SYNC (agent_sync.yml) +# ========================================== +agent-backlog-sync: + stage: sync + image: python:3.12 + rules: + - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' + changes: + - "docs/moon/ROADMAP.md" + - "docs/moon/CHANGELOG.md" + - if: '$CI_PIPELINE_SOURCE == "web"' + before_script: + - pip install uv + - uv sync --project git + script: + - uv run --project git python -c "import json,pathlib; json.loads(pathlib.Path('git/config/project_labels.json').read_text())" + - uv run --project git python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('git/config/automation_rules.yaml').read_text())" + - uv run --project git python -m git.scripts.sync_backlog --repo-owner ACFHarbinger --repo-name github-pages --project-id "$PROJECT_ID" + variables: + GITHUB_TOKEN: $PROJECT_AUTOMATION_TOKEN diff --git a/.gitlab/issue_templates/Bug_Report.md b/.gitlab/issue_templates/Bug_Report.md new file mode 100644 index 0000000..2781491 --- /dev/null +++ b/.gitlab/issue_templates/Bug_Report.md @@ -0,0 +1,26 @@ + + +### Affected area + +- [ ] Posts +- [ ] Reports +- [ ] Projects +- [ ] Tools +- [ ] Media +- [ ] About / Other +- [ ] Notebooks (`notebooks/`) +- [ ] Build / tooling / CI +- [ ] Other / unknown + +### What happened? + + +### Steps to reproduce + + +### Relevant log output +```shell + +``` + +/label ~"bug" ~"triage" diff --git a/.gitlab/issue_templates/Feature_Request.md b/.gitlab/issue_templates/Feature_Request.md new file mode 100644 index 0000000..80205ae --- /dev/null +++ b/.gitlab/issue_templates/Feature_Request.md @@ -0,0 +1,17 @@ +### What problem does this solve? + +### Proposed solution + +### Affected area + +- [ ] Posts +- [ ] Reports +- [ ] Projects +- [ ] Tools +- [ ] Media +- [ ] About / Other +- [ ] Notebooks (`notebooks/`) +- [ ] Build / tooling / CI +- [ ] Other / unknown + +/label ~"enhancement" ~"triage" diff --git a/.gitlab/merge_request_templates/Default.md b/.gitlab/merge_request_templates/Default.md new file mode 100644 index 0000000..a68fef2 --- /dev/null +++ b/.gitlab/merge_request_templates/Default.md @@ -0,0 +1,26 @@ +# Pull Request + +## Summary + + + +## Affected Area(s) + +- [ ] Site content (`app/content/**`) +- [ ] Components / styling (`src/`) +- [ ] Notebooks / research (`notebooks/`, `docs/moon/research/`, `docs/moon/reports/`) +- [ ] Tooling / CI / docs + +## Type of Change + +- [ ] 🐛 Bug fix +- [ ] ✨ New feature +- [ ] ♻️ Refactor +- [ ] 📚 Content / documentation +- [ ] 🔧 Tooling / CI + +## Verification + +- [ ] `npm run lint` and `npm test` pass. +- [ ] `npm run build` succeeds (static export). +- [ ] Manually checked the affected page(s) with `npm run dev`. diff --git a/.gitmessage b/.gitmessage new file mode 100644 index 0000000..3f33c08 --- /dev/null +++ b/.gitmessage @@ -0,0 +1,2 @@ + +Co-authored-by: Gemini Code Assist diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..a5acf57 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,80 @@ +# Pre-commit hooks for this repository. +# +# Install once: pip install pre-commit && pre-commit install +# Run manually: pre-commit run --all-files +# Update hooks: pre-commit autoupdate +# +# Gate philosophy: +# - Blocking: eslint, ruff +# - Advisory: tsc, mypy + +repos: + # ── General file hygiene ───────────────────────────────────────────────── + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + args: [--allow-multiple-documents] + - id: check-json + - id: check-merge-conflict + - id: check-added-large-files + args: [--maxkb=5000] + - id: mixed-line-ending + args: [--fix=lf] + + # ── TypeScript / React: eslint (blocking) ──────────────────────────────── + - repo: local + hooks: + - id: eslint + name: eslint (src/, app/) + language: system + entry: npm run lint -- + pass_filenames: false + - id: tsc-check + name: TypeScript type check (advisory) + language: system + entry: bash -c 'npx tsc --noEmit' + files: \.(ts|tsx)$ + pass_filenames: false + + # ── Python (notebooks/): ruff (blocking) + mypy (advisory) ─────────────── + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.0 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + types_or: [python, pyi] + files: ^notebooks/ + - id: ruff-format + types_or: [python, pyi] + files: ^notebooks/ + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.10.1 + hooks: + - id: mypy + name: mypy (notebooks/) + args: [--ignore-missing-imports, --no-error-summary] + files: ^notebooks/.*\.py$ + + # ── Markdown: link checker ─────────────────────────────────────────────── + - repo: https://github.com/lycheeverse/lychee + rev: lychee-v0.15.1 + hooks: + - id: lychee + name: lychee (markdown link check) + args: + - --verbose + - --no-progress + - --include-verbatim + files: \.(md|markdown)$ + pass_filenames: true + + # ── Jupyter: strip notebook output from git ────────────────────────────── + - repo: https://github.com/kynan/nbstripout + rev: 0.7.1 + hooks: + - id: nbstripout + files: ^notebooks/.*\.ipynb$ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ee17db1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +[//]: # "Include the content from the master AGENTS file" +[//]: # "Note: This is a pointer to the source of truth" +[//]: # "Content from .agent/AGENTS.md" + +[Include .agent/AGENTS.md](.agent/AGENTS.md) diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..ee17db1 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,5 @@ +[//]: # "Include the content from the master AGENTS file" +[//]: # "Note: This is a pointer to the source of truth" +[//]: # "Content from .agent/AGENTS.md" + +[Include .agent/AGENTS.md](.agent/AGENTS.md) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0ad25db --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md index 1e1a4b1..2bf5f4f 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,397 @@ -
+
- +# 📓 GitHub Pages — Personal Site & Knowledge Base -# Github Pages +**ACFHarbinger's personal blog and knowledge base: posts, reports, project write-ups, and tool notes, statically exported and deployed to GitHub Pages.** -My own personal [github pages blog](https://acfharbinger.github.io/github-pages/)! +Live Site +CI +Deploy +License: AGPL v3 -
+
- +Next.js +React +TypeScript +Tailwind CSS +Python + +
+ +Vitest +Testing Library +MSW +Cypress +ESLint +uv +Ruff +Jupyter + + + +--- + +## Overview + +This repository builds [acfharbinger.github.io/github-pages](https://acfharbinger.github.io/github-pages/) — a personal site combining a blog with a small knowledge base: + +| Section | What lives there | +| --- | --- | +| **Posts** | Shorter-form writing | +| **Reports** | Longer, structured write-ups (e.g. audio signal processing, the PCVRP report) | +| **Projects** | Write-ups of things I've built | +| **Tools** | Notes on tools/utilities | +| **Media** | Media-focused content | +| **About / Other** | Everything else | + +It's a fully static [Next.js](https://nextjs.org/) export (`output: 'export'`) — no server, no API routes, no database. Content is authored as Markdown and rendered at build time. ## Tech Stack -- [HTML - HyperText Markup Language](https://html.com/) -- [CSS - Cascading Style Sheets](https://www.w3.org/Style/CSS/Overview.en.html) -- [TypeScript Programming Language](https://www.typescriptlang.org/) -- [JavaScript Programming Language](https://www.javascript.com/) -- [Tailwind CSS Framework](https://tailwindcss.com/) -- [React JavaScript/TypeScript Framework](https://react.dev/) -- [Next.JS React Framework](https://nextjs.org/) -
+- **Framework:** [Next.js](https://nextjs.org/) (App Router) + [React](https://react.dev/) + [TypeScript](https://www.typescriptlang.org/) +- **Styling:** [Tailwind CSS](https://tailwindcss.com/) +- **Content:** Markdown, parsed with [`gray-matter`](https://github.com/jonschlinkert/gray-matter) and [`remark`](https://github.com/remarkjs/remark) +- **Testing:** [Vitest](https://vitest.dev/) + [Testing Library](https://testing-library.com/) (unit/integration) + [MSW](https://mswjs.io/) (network mocking) · [Cypress](https://www.cypress.io/) (e2e/smoke) +- **Linting:** [ESLint](https://eslint.org/) (`eslint-config-next`) +- **Icons:** [lucide-react](https://lucide.dev/) +- **Research workspace:** [Python](https://www.python.org/) 3.11+ managed with [uv](https://github.com/astral-sh/uv), used for the analysis behind some reports + +## Project Structure + +``` +. +├── app/ # Next.js App Router: routes + layouts +│ └── content/ # Markdown content, one folder per section +│ ├── posts/ +│ ├── reports/ +│ ├── projects/ +│ ├── tools/ +│ ├── media/ +│ ├── about/ +│ └── other/ +├── src/ +│ ├── components/ # React components (layout, ui, content wrappers) +│ └── styles/ +├── lib/ # Markdown loading/parsing helpers +├── test/ +│ ├── unit/ # Vitest + Testing Library specs, mirrors src/components/ +│ ├── integration/ # Vitest + Testing Library + MSW specs +│ └── cypress/ # e2e/ (one spec per section) + smoke/ +├── infra/ # Optional self-hosting / alt-deploy tooling +│ ├── global/ # External (public-facing) deploy & host configs +│ ├── private/ # Internal (developer-only) tooling +│ ├── cloud/ # Managed cloud static-host configs +│ └── server/ # nginx / Envoy reverse-proxy configs +├── notebooks/ # Python/uv workspace for report research +├── docs/moon/research/ # Longer design/research write-ups +└── public/ # Static assets +``` + +## Getting Started + +Prerequisites: [Node.js](https://nodejs.org/) 20+ and npm. + +```bash +git clone https://github.com/ACFHarbinger/github-pages.git +cd github-pages +npm install +npm run dev +``` + +The dev server runs at `http://localhost:3000/github-pages` (the `basePath` matches the GitHub Pages deployment path). + +### Building + +```bash +npm run build # static export to out/ +npm run deploy # build + touch out/.nojekyll +npm start # serve the out/ export locally +``` + +## Testing + +```bash +npm run lint # ESLint +npx tsc --noEmit # type check +npm test # Vitest: unit + integration +npm run test:watch # Vitest in watch mode +npm run test:unit # Vitest: test/unit/ only +npm run test:integration # Vitest: test/integration/ only +npm run cypress:open # Cypress e2e + smoke (interactive) +npm run cypress:run # Cypress e2e + smoke (headless) +npm run cypress:e2e # Cypress: test/cypress/e2e/ only +npm run cypress:smoke # Cypress: test/cypress/smoke/ only +``` + +Cypress runs against a served build, so run `npm run build && npm start` (or `npm run dev`) in another terminal first — see `test/cypress/cypress.config.js` for the `baseUrl`. `npm run build`'s `postbuild` step symlinks `out/github-pages -> .` so `npm start` (a plain static file server) answers under `/github-pages`, matching how GitHub Pages actually serves the site. + +## Notebooks - +`notebooks/` is a standalone Python workspace (not part of the site build) used to run the analysis behind some reports: + +```bash +cd notebooks +uv sync --extra dev +uv run jupyter lab +``` + +## Deployment + +Pushing to `main` triggers [`.github/workflows/deploy.yml`](.github/workflows/deploy.yml), which builds the static export and publishes `out/` to the `gh-pages` branch via [`peaceiris/actions-gh-pages`](https://github.com/peaceiris/actions-gh-pages). [`.github/workflows/ci.yml`](.github/workflows/ci.yml) runs lint/typecheck/unit/e2e on every push and PR. + +Optional alternatives to GitHub Pages (not used by the default workflow) live under [`infra/`](infra/README.md): + +| Path | Purpose | +| --- | --- | +| [`infra/global/docker/`](infra/global/docker/) | Build + serve the export locally with Docker Compose / nginx | +| [`infra/global/k8s/`](infra/global/k8s/) · [`helm/`](infra/global/helm/) · [`terraform/`](infra/global/terraform/) · [`ansible/`](infra/global/ansible/) | Self-host the nginx container on a cluster or plain host | +| [`infra/cloud/`](infra/cloud/) | AWS (S3 + CloudFront / Serverless), Azure Static Web Apps, Firebase configs | +| [`infra/private/webpack/`](infra/private/webpack/) | Developer-only Webpack experiments | +| [`infra/private/wordpress/`](infra/private/wordpress/) | WordPress theme scaffolding for local/CMS experiments | +| [`infra/server/nginx/`](infra/server/nginx/) · [`proxy/`](infra/server/proxy/) | Standalone nginx and Envoy reverse-proxy configs | + +Example local self-host: + +```bash +docker compose -f infra/global/docker/docker-compose.yml up --build +``` + +## License + +[AGPL-3.0](LICENSE) --- -Get help: [Post in github's discussion board](https://github.com/orgs/skills/discussions/categories/github-pages) • [Review the GitHub status page](https://www.githubstatus.com/) -
+Get help: [GitHub Discussions](https://github.com/orgs/skills/discussions/categories/github-pages) • [GitHub Status](https://www.githubstatus.com/) + +## Documentation website + +The documentation website is this Next.js application. Markdown content is read during the Next.js build and emitted as static HTML, so documentation deploys to GitHub Pages without a runtime database. + +### Launch locally + +```bash +npm install +npm run dev +``` + +Open `http://localhost:3000/github-pages/`. The `/github-pages` base path mirrors the public deployment URL. For the production export: + +```bash +npm run build +npm start +``` + +Open `http://localhost:3000/github-pages/`. The `postbuild` script creates `out/github-pages -> .` so local static serving reproduces GitHub Pages path resolution. + +### Documentation routes + +| URL | Purpose | Source | +| --- | --- | --- | +| `/content/posts/` | Short technical notes | `app/content/posts/` | +| `/content/reports/` | Long-form research reports | `app/content/reports/` | +| `/content/projects/` | Project case studies | `app/content/projects/` | +| `/content/tools/` | Tool and workflow notes | `app/content/tools/` | +| `/content/media/` | Anime, film, and television notes | `app/content/media/` | +| `/content/about/` | Biography and contact context | `app/content/about/` | +| `/content/other/` | Miscellaneous essays | `app/content/other/` | +| `/` | Interactive research observatory | `app/page.tsx` | + +### Authoring a report + +1. Choose the section matching visitor intent. +2. Create a Markdown file with stable filename and valid front matter. +3. Write the claim, assumptions, evidence, limitations, and next action before adding effects. +4. Link local figures and licensed assets with stable relative paths. +5. Run development, typecheck, tests, build, and benchmark. +6. Add a roadmap entry when the report introduces a new interactive commitment. + +Do not put secrets, private API responses, unlicensed images, or notebook runtime code in a page. A private experiment needs a reproducible public summary or labelled recorded result. + +### Front matter + +```yaml +title: A descriptive visitor-facing title +date: 2026-08-08 +description: A one-sentence summary used in indexes and metadata. +tags: + - optimization + - machine-learning +``` + +Keep titles concise for cards and browser tabs. Use stable lowercase tags. Dates represent publication or substantive revision. + +## Project structure in detail + +```text +. +├── app/ # Next.js routes, layouts, and content pages +├── benchmark/ # static export performance harness +│ ├── measure.mjs +│ └── README.md +├── docs/ # architecture, testing, standards, moon roadmaps +│ ├── ARCHITECTURE.md +│ ├── adr/ +│ └── moon/{research,roadmaps}/ +├── infra/ # optional self-hosting / alt-deploy tooling +│ ├── global/ # external public-facing deploy & host configs +│ │ ├── ansible/ docker/ helm/ k8s/ terraform/ +│ ├── private/ # internal developer-only tooling +│ │ └── webpack/ wordpress/ +│ ├── cloud/ # AWS / Azure / Firebase / Serverless +│ └── server/ # nginx / Envoy reverse-proxy configs +│ └── nginx/ proxy/ +├── lib/ # build-time Markdown/front-matter helpers +├── notebooks/ # independent Python/uv research workspace +├── public/ # static, licensed browser assets +├── src/ # components, state, interfaces, simulations +│ ├── aurelia/ # optional Aurelia islands +│ ├── components/ # audio, books, canvas, games, graph, image, +│ │ ├── maps/ models/ routes/ video/ +│ │ ├── layout/ ui/ +│ │ └── ... +│ ├── configs/constants/context/enums/hooks/interfaces/ +│ ├── redux/ routes/ types/ utils/ +│ └── simulations/{context,generator,repository,scenarios}/ +├── test/{unit,integration,cypress}/ +└── package.json +``` + +### Choosing a source directory + +- Put a shared type in `src/interfaces`. +- Put a cross-route preference in `src/redux`. +- Put a primitive in `src/components/ui`. +- Put a domain feature in its matching `src/components` subdirectory. +- Put deterministic data in `src/simulations/scenarios`. +- Put simulation contracts in `src/simulations/repository`. +- Put lifecycle orchestration in `src/simulations/context`. +- Put pure transformations in `src/utils`. +- Put route composition in `app/` or `src/routes`. + +Avoid adding a generic `components/interactive` directory. Extract the smallest shared contract or hook when a feature crosses domains, while leaving rendering with the owning domain. + +## Interactive research features + +| Area | Current foundation | Planned slice | +| --- | --- | --- | +| Fleet routing | SVG/canvas routes and deterministic convergence | playback, constraints, solver/Pareto comparisons | +| Machine learning | spectrum and research visual language | policy replay, model cards, local fixtures | +| Game development | prototype card and case-study framing | isolated playable mechanic and devlog graph | +| 3D | capability-aware procedural hero model | reusable glTF viewer and quality tiers | +| 360 media | static-first roadmap contract | panorama room with hotspot list | +| Reading/media | shelf, mosaic, reel, and content routes | source graph and timeline | +| Audio | static spectrum presentation | explicit-gesture Web Audio analysis | + +Every feature begins with a semantic fallback and visitor question. See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) and [`docs/moon/roadmaps/research_derived_interactions.md`](docs/moon/roadmaps/research_derived_interactions.md). + +## Full quality workflow + +```bash +npm run lint +npx tsc --noEmit --incremental false +npm test +npm run build +npm run benchmark +``` + +For browser smoke tests, run `npm run build` and `npm start`, then use `npm run cypress:smoke` in another terminal. The benchmark requires `out/`; it writes ignored `benchmark/results/latest.json` with export bytes, JavaScript/CSS totals, route responses, largest assets, and budget checks. It does not measure real-user Web Vitals or GPU memory. + +## Benchmark interpretation + +Investigate failures by checking eager dependencies, media sizes, optional-island boundaries, duplicated fallbacks, and documented value. Initial guardrails are 200 kB JavaScript, 80 kB CSS, 2 MB homepage transfer, 3 MB route response, and 1 MB largest asset. Exceptions need a reduced/static alternative and follow-up issue. + +## Roadmap and issue workflow + +1. Find the workstream in [`docs/moon/ROADMAP.md`](docs/moon/ROADMAP.md). +2. Read its detailed file under [`docs/moon/roadmaps/`](docs/moon/roadmaps/). +3. Create or update the matching GitHub issue and project item. +4. Implement the smallest fallback-first slice. +5. Add tests and benchmark evidence. +6. Update status, changelog, architecture notes, and issue comments. +7. Commit with a focused message. + +Use `Backlog` for unstarted work, `In progress` for an active or partial slice, `In review` for a finished change awaiting review, and `Done` only when acceptance criteria and evidence are complete. + +## Git workflow + +```bash +git status +git diff --check +git add +git commit -m "feat: describe the visitor-facing change" +``` + +Do not commit `out/`, `.next/`, `tsconfig.tsbuildinfo`, benchmark result JSON, screenshots, videos, local credentials, or notebook caches. Do commit source fixtures, documentation, and reproducibility instructions. + +## Environment and configuration + +The website has no required runtime environment variables. `stack/next/next.config.js (re-exported at root)` defines the static export and GitHub Pages base path. Browser-only APIs belong inside client components and effects, never during static rendering. + +If an optional integration needs a token, prefer a public build-time fixture or remove it from the default site. Never place a secret in `NEXT_PUBLIC_*`, Markdown, `public/`, or a committed notebook. + +## Content and media licensing + +Before adding an image, model, panorama, audio clip, font, or video: + +- record creator, source URL, license, and retrieval date; +- confirm redistribution is allowed; +- provide alt text, captions, poster, or transcript; +- provide a low-cost/static alternative; +- avoid trackers or remote code; +- keep private research data out of public assets. + +## Accessibility expectations + +The site supports keyboard navigation, visible focus, semantic landmarks, reduced motion, light/dark themes, text summaries, list/table equivalents, and graceful no-WebGL behavior. Interactive charts must not require hover. Audio requires a user gesture. 3D controls need reset/pause and an ordered annotation list. An effect that cannot meet these requirements is optional research, not a default feature. + +## Troubleshooting quick reference + +| Symptom | Likely cause | Fix | +| --- | --- | --- | +| `/` is blank in dev | Base path omitted | Open `/github-pages/`. | +| `npm start` cannot find export | Build has not run | Run `npm run build`. | +| Benchmark says `out/` missing | No production export | Run `npm run benchmark:build`. | +| TypeScript sees stale paths | Simulation directory moved | Check `repository` and `scenarios`. | +| Three.js view is absent | Capability/reduced tier/failure | Use static poster and inspect console. | +| Cypress cannot connect | Server is not running | Run `npm start` in another terminal. | +| Markdown route is missing | Invalid slug/front matter | Inspect filename and run build. | +| Vitest network warning | MSW rejected unhandled request | Add a fixture only when intended. | + +## Documentation index + +- [Architecture](docs/ARCHITECTURE.md) +- [Moon master roadmap](docs/moon/ROADMAP.md) +- [Research-derived roadmap](docs/moon/roadmaps/research_derived_interactions.md) +- [Interactive research report](docs/moon/research/Interactive%20Features%20and%20Visual%20Storytelling%20Research.md) +- [Testing guide](docs/TESTING.md) +- [Development guide](docs/DEVELOPMENT.md) +- [Documentation standards](docs/DOCUMENTATION_STANDARDS.md) +- [Dependency policy](docs/DEPENDENCY_POLICY.md) +- [Troubleshooting](docs/TROUBLESHOOTING.md) + +## Maintainer release checklist + +- [ ] Content builds with no missing static routes. +- [ ] Lint and typecheck pass. +- [ ] Unit and integration tests pass. +- [ ] Browser smoke tests pass against the production export. +- [ ] Benchmark budgets are green or exceptions documented. +- [ ] New assets have license/provenance records. +- [ ] Reduced-motion, keyboard, no-WebGL, and failure paths exercised. +- [ ] Changelog and roadmap status are current. +- [ ] GitHub issue/project status reflects reality. +- [ ] Commit history explains material architectural decisions. + +## Why this structure is explicit + +This is a personal site and a public notebook for engineering work. Explicit boundaries show a beautiful result without hiding assumptions, costs, or limitations. A new route story can begin as Markdown and SVG, grow into a focused React island, and only then become a 3D, audio, map, or GPU experiment if evidence and budgets justify it. + +That progression protects readers on older devices, keeps GitHub Pages reliable, and makes research claims easier to audit. It gives collaborators a predictable place to add work: contracts in interfaces, behavior in the owning domain, computation in simulations/workers, and evidence in reports and roadmaps. + +## README history + +| Date | Revision | Change | +| --- | --- | --- | +| 2026-08-08 | R3 | Expanded structure, launch instructions, benchmark workflow, feature guidance, issue workflow, accessibility, licensing, troubleshooting, and release procedures. | diff --git a/app/ClientLayoutWrapper.tsx b/app/ClientLayoutWrapper.tsx index 67d4fc5..ba24940 100644 --- a/app/ClientLayoutWrapper.tsx +++ b/app/ClientLayoutWrapper.tsx @@ -3,24 +3,29 @@ import React, { useState, useEffect } from 'react'; import { X } from 'lucide-react'; import { usePathname } from 'next/navigation'; -import Sidebar from '../src/components/layout/Sidebar'; -import Footer from '../src/components/layout/Footer'; -import Header from '../src/components/layout/Header'; +import Sidebar from '../src/frameworks/react/components/layout/Sidebar'; +import Footer from '../src/frameworks/react/components/layout/Footer'; +import Header from '../src/frameworks/react/components/layout/Header'; +import { useAppDispatch } from '../src/libraries/redux/store/hooks'; +import { setTheme } from '../src/libraries/redux/actions/appActions'; +import { persistTheme, readStoredTheme } from '../src/libraries/redux/services/persistence'; +import ReduxProvider from '../src/libraries/redux/store/ReduxProvider'; interface ClientLayoutWrapperProps { children: React.ReactNode; } -const ClientLayoutWrapper: React.FC = ({ children }) => { +const ClientLayoutContent: React.FC = ({ children }) => { const [darkMode, setDarkMode] = useState(true); // Default to true (Dark Mode) const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(true); const pathname = usePathname(); + const dispatch = useAppDispatch(); // --- getActiveSection Logic --- - const getActiveSection = (path: string) => { - if (path === '/') return 'home'; + const getActiveSection = (path: string | null) => { + if (!path || path === '/') return 'home'; const match = path.match(/^\/(?:content\/)?([a-z-]+)/); if (match) { return match[1]; @@ -32,25 +37,23 @@ const ClientLayoutWrapper: React.FC = ({ children }) = // --- Dark Mode Logic --- useEffect(() => { - const storedTheme = localStorage.getItem('theme'); + const storedTheme = readStoredTheme(); // If stored as 'light', set false. Otherwise default to true (Dark). - if (storedTheme === 'light') { - setDarkMode(false); - } else { - setDarkMode(true); - } + setDarkMode(storedTheme !== 'light'); }, []); useEffect(() => { if (darkMode) { document.documentElement.classList.add('dark'); - localStorage.setItem('theme', 'dark'); + persistTheme('dark'); + dispatch(setTheme('dark')); } else { document.documentElement.classList.remove('dark'); - localStorage.setItem('theme', 'light'); + persistTheme('light'); + dispatch(setTheme('light')); } - }, [darkMode]); + }, [darkMode, dispatch]); const toggleTheme = () => setDarkMode(!darkMode); const toggleSidebarCollapsed = () => setIsSidebarCollapsed(!isSidebarCollapsed); @@ -59,9 +62,7 @@ const ClientLayoutWrapper: React.FC = ({ children }) = const toggleMenu = () => setMobileMenuOpen(!mobileMenuOpen); useEffect(() => { - if (mobileMenuOpen) { - setMobileMenuOpen(false); - } + setMobileMenuOpen(false); }, [pathname]); @@ -125,4 +126,8 @@ const ClientLayoutWrapper: React.FC = ({ children }) = ); }; -export default ClientLayoutWrapper; \ No newline at end of file +const ClientLayoutWrapper: React.FC = ({ children }) => ( + {children} +); + +export default ClientLayoutWrapper; diff --git a/app/content/about/page.tsx b/app/content/about/page.tsx index fa343a8..4c9acf2 100644 --- a/app/content/about/page.tsx +++ b/app/content/about/page.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Briefcase, GraduationCap, Heart, User, MapPin } from 'lucide-react'; -import SectionHeading from '@/src/components/ui/SectionHeading'; -import GlassCard from '@/src/components/ui/GlassCard'; +import SectionHeading from '@/src/frameworks/react/components/ui/SectionHeading'; +import GlassCard from '@/src/frameworks/react/components/ui/GlassCard'; import pageImage from '@/assets/images/steamuserimages-a.akamaihd.jpeg'; export default function AboutPage() { @@ -30,9 +30,9 @@ export default function AboutPage() { } />

- I'm currently a Doctoral Researcher at INESC-ID - — where I'm developing new Deep Reinforcement Learning and Operations Research methods to solve Combinatorial Optimization tasks — and an Invited Assistant Professor at the Computer Science and Engineering - department of IST, where I teach courses + I'm currently a Doctoral Researcher at INESC-ID + — where I'm developing new Deep Reinforcement Learning and Operations Research methods to solve Combinatorial Optimization tasks — and an Invited Assistant Professor at the Computer Science and Engineering + department of IST, where I teach courses about Distributed Systems , Cloud Computing and Virtualization ,and Computer Organization. @@ -175,4 +175,4 @@ export default function AboutPage() { ); -} \ No newline at end of file +} diff --git a/app/content/media/page.tsx b/app/content/media/page.tsx index fb11071..0464291 100644 --- a/app/content/media/page.tsx +++ b/app/content/media/page.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Gamepad2, Film, BookOpen, Music } from 'lucide-react'; -import SectionHeading from '@/src/components/ui/SectionHeading'; -import GlassCard from '@/src/components/ui/GlassCard'; +import SectionHeading from '@/src/frameworks/react/components/ui/SectionHeading'; +import GlassCard from '@/src/frameworks/react/components/ui/GlassCard'; import pageImage from '@/assets/images/dg7za3r-5c28683b-d2e4-4018-bf8b-eaea55cde631.png'; export default function MediaPage() { diff --git a/app/content/other/[slug]/page.tsx b/app/content/other/[slug]/page.tsx index 3cc65b6..f9dc404 100644 --- a/app/content/other/[slug]/page.tsx +++ b/app/content/other/[slug]/page.tsx @@ -4,7 +4,7 @@ import { ArrowLeft } from 'lucide-react'; import Link from 'next/link'; import fs from 'fs'; import path from 'path'; -import ReportWrapper from '@/src/components/ReportWrapper'; +import ReportWrapper from '@/src/frameworks/react/ReportWrapper'; // Configuration: Map the slug to the Base Filename (no extension) and Title const OTHER_DATA: Record = { diff --git a/app/content/other/page.tsx b/app/content/other/page.tsx index 5a767f6..b5ac724 100644 --- a/app/content/other/page.tsx +++ b/app/content/other/page.tsx @@ -3,8 +3,8 @@ import React from 'react'; import Link from 'next/link'; import { FileText, ExternalLink } from 'lucide-react'; -import SectionHeading from '@/src/components/ui/SectionHeading'; -import GlassCard from '@/src/components/ui/GlassCard'; +import SectionHeading from '@/src/frameworks/react/components/ui/SectionHeading'; +import GlassCard from '@/src/frameworks/react/components/ui/GlassCard'; import pageImage from '@/assets/images/GcxP4GkXMAAX7az.jpeg'; const OTHER_ITEMS = [ diff --git a/app/content/posts/[slug]/page.tsx b/app/content/posts/[slug]/page.tsx index 59120f0..e092009 100644 --- a/app/content/posts/[slug]/page.tsx +++ b/app/content/posts/[slug]/page.tsx @@ -4,7 +4,7 @@ import Link from 'next/link'; import { ArrowLeft } from 'lucide-react'; import fs from 'fs'; import path from 'path'; -import PostWrapper from '@/src/components/PostWrapper'; +import PostWrapper from '@/src/frameworks/react/PostWrapper'; // Define the type for the URL parameters interface PostPageProps { diff --git a/app/content/posts/page.tsx b/app/content/posts/page.tsx index 3c1fd48..9334729 100644 --- a/app/content/posts/page.tsx +++ b/app/content/posts/page.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { PenTool, Calendar, ArrowRight } from 'lucide-react'; -import GlassCard from '@/src/components/ui/GlassCard'; -import SectionHeading from '@/src/components/ui/SectionHeading'; -import Badge from '@/src/components/ui/Badge'; +import GlassCard from '@/src/frameworks/react/components/ui/GlassCard'; +import SectionHeading from '@/src/frameworks/react/components/ui/SectionHeading'; +import Badge from '@/src/frameworks/react/components/ui/Badge'; import pageImage from '@/assets/images/G7HNekqXUAAueb6.jpg'; diff --git a/app/content/projects/page.tsx b/app/content/projects/page.tsx index 12820c6..181b59e 100644 --- a/app/content/projects/page.tsx +++ b/app/content/projects/page.tsx @@ -1,9 +1,9 @@ import React from 'react'; import Link from 'next/link'; import { Brain, Code, Cpu, Gamepad2, FileText, BookOpen } from 'lucide-react'; -import SectionHeading from '@/src/components/ui/SectionHeading'; -import GlassCard from '@/src/components/ui/GlassCard'; -import Badge from '@/src/components/ui/Badge'; +import SectionHeading from '@/src/frameworks/react/components/ui/SectionHeading'; +import GlassCard from '@/src/frameworks/react/components/ui/GlassCard'; +import Badge from '@/src/frameworks/react/components/ui/Badge'; import pageImage from '@/assets/images/Ella-Purnell-Jinx-Arcane-League-of-Legends.webp'; // Common style for links @@ -58,7 +58,7 @@ export default function ProjectsPage() {

CSE Master of Science (MSc) Dissertation

- Thesis: "Leveraging Deep Unsupervised Models Towards Learning Robust Multimodal Representations". Developed and compared new Multimodal Deep Unsupervised Models. + Thesis: “Leveraging Deep Unsupervised Models Towards Learning Robust Multimodal Representations”. Developed and compared new Multimodal Deep Unsupervised Models.

@@ -255,4 +255,4 @@ export default function ProjectsPage() {
); -} \ No newline at end of file +} diff --git a/app/content/reports/[slug]/page.tsx b/app/content/reports/[slug]/page.tsx index 85aeffb..5c3c3dc 100644 --- a/app/content/reports/[slug]/page.tsx +++ b/app/content/reports/[slug]/page.tsx @@ -4,7 +4,7 @@ import { ArrowLeft } from 'lucide-react'; import Link from 'next/link'; import fs from 'fs'; import path from 'path'; -import ReportWrapper from '@/src/components/ReportWrapper'; +import ReportWrapper from '@/src/frameworks/react/ReportWrapper'; // Configuration: Map the slug to the Base Filename (no extension) and Title const REPORTS_DATA: Record = { diff --git a/app/content/reports/page.tsx b/app/content/reports/page.tsx index 51e84de..6fd1a3c 100644 --- a/app/content/reports/page.tsx +++ b/app/content/reports/page.tsx @@ -3,8 +3,8 @@ import React from 'react'; import Link from 'next/link'; import { FileText, ExternalLink } from 'lucide-react'; -import SectionHeading from '@/src/components/ui/SectionHeading'; -import GlassCard from '@/src/components/ui/GlassCard'; +import SectionHeading from '@/src/frameworks/react/components/ui/SectionHeading'; +import GlassCard from '@/src/frameworks/react/components/ui/GlassCard'; import pageImage from '@/assets/images/Jinx-League-of-Legends-League-of-Legends-arcane-Netflix-TV-Series-tv-series-video-game-characters-2233556.jpg'; // Note: 'id' matches the keys in page.tsx 'REPORTS_DATA' diff --git a/app/content/tools/page.tsx b/app/content/tools/page.tsx index 517263f..52954a9 100644 --- a/app/content/tools/page.tsx +++ b/app/content/tools/page.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { Terminal, Palette, Box, Database, Globe, Brain } from 'lucide-react'; -import SectionHeading from '@/src/components/ui/SectionHeading'; -import GlassCard from '@/src/components/ui/GlassCard'; -import Badge from '@/src/components/ui/Badge'; +import SectionHeading from '@/src/frameworks/react/components/ui/SectionHeading'; +import GlassCard from '@/src/frameworks/react/components/ui/GlassCard'; +import Badge from '@/src/frameworks/react/components/ui/Badge'; import pageImage from '@/assets/images/maxresdefault.jpg'; export default function ToolsPage() { diff --git a/app/globals.css b/app/globals.css index 7e1120c..0ed6b1e 100644 --- a/app/globals.css +++ b/app/globals.css @@ -11,7 +11,162 @@ html { @layer base { body { @apply bg-white dark:bg-black text-slate-900 dark:text-white; + background-image: + radial-gradient(circle at 72% 12%, rgb(59 130 246 / 0.08), transparent 32rem), + radial-gradient(circle at 30% 70%, rgb(168 85 247 / 0.06), transparent 34rem); } + + :focus-visible { + outline: 2px solid rgb(56 189 248); + outline-offset: 4px; + } +} + +:root { + --observatory-cyan: #38bdf8; + --observatory-violet: #8b5cf6; + --observatory-pink: #f472b6; + --observatory-ink: #07111f; + --observatory-panel: rgb(255 255 255 / 0.68); + --observatory-border: rgb(15 23 42 / 0.1); +} + +.dark { + --observatory-panel: rgb(9 18 35 / 0.72); + --observatory-border: rgb(148 163 184 / 0.16); +} + +.hero-observatory { + position: relative; + display: grid; + grid-template-columns: minmax(0, 1.1fr) minmax(280px, 0.9fr); + align-items: center; + gap: clamp(2rem, 5vw, 5rem); + min-height: 590px; + padding: clamp(2rem, 5vw, 4.5rem) 0; +} + +.hero-observatory::before { + content: ''; + position: absolute; + inset: 8% -20% 4% 18%; + z-index: -1; + background: radial-gradient(circle at 70% 42%, rgb(56 189 248 / 0.12), transparent 34%), radial-gradient(circle at 52% 58%, rgb(139 92 246 / 0.12), transparent 42%); + filter: blur(24px); +} + +.hero-observatory__copy { display: flex; flex-direction: column; align-items: flex-start; gap: 1.3rem; } +.status-pulse { display: inline-block; width: .45rem; height: .45rem; margin-right: .4rem; border-radius: 999px; background: #22c55e; box-shadow: 0 0 0 5px rgb(34 197 94 / .12); } +.hero-kicker { color: #64748b; font-size: .72rem; font-weight: 800; letter-spacing: .18em; text-transform: uppercase; } +.dark .hero-kicker { color: #94a3b8; } +.hero-title { max-width: 11ch; color: #0f172a; font-size: clamp(3.6rem, 7.4vw, 6.7rem); font-weight: 800; line-height: .92; letter-spacing: -.065em; } +.dark .hero-title { color: #f8fafc; } +.hero-title span { color: transparent; background: linear-gradient(105deg, #0284c7 8%, #7c3aed 52%, #db2777); background-clip: text; -webkit-background-clip: text; } +.dark .hero-title span { background-image: linear-gradient(105deg, #67e8f9 8%, #a78bfa 52%, #f9a8d4); } +.hero-summary { max-width: 39rem; color: #475569; font-size: clamp(1rem, 1.8vw, 1.16rem); line-height: 1.75; } +.dark .hero-summary { color: #a8b4c7; } +.hero-summary strong { color: #172554; font-weight: 650; } +.dark .hero-summary strong { color: #e0e7ff; } +.hero-actions { display: flex; flex-wrap: wrap; gap: .8rem; } +.hero-action { display: inline-flex; align-items: center; gap: .5rem; padding: .82rem 1.15rem; border: 1px solid var(--observatory-border); border-radius: .85rem; font-size: .88rem; font-weight: 700; transition: transform .2s ease, box-shadow .2s ease, background .2s ease; } +.hero-action:hover { transform: translateY(-2px); } +.hero-action--primary { color: white; border-color: transparent; background: linear-gradient(110deg, #0369a1, #6d28d9); box-shadow: 0 12px 30px rgb(79 70 229 / .2); } +.hero-action--secondary { color: #334155; background: var(--observatory-panel); backdrop-filter: blur(16px); } +.dark .hero-action--secondary { color: #e2e8f0; } +.hero-metrics { display: flex; gap: 0; margin-top: .65rem; } +.hero-metrics div { min-width: 6.8rem; padding-right: 1.4rem; margin-right: 1.4rem; border-right: 1px solid var(--observatory-border); } +.hero-metrics div:last-child { border: 0; margin: 0; padding: 0; } +.hero-metrics dt { color: #94a3b8; font-size: .63rem; font-weight: 800; letter-spacing: .12em; text-transform: uppercase; } +.hero-metrics dd { margin-top: .3rem; color: #1e293b; font-size: .9rem; font-weight: 750; } +.dark .hero-metrics dd { color: #e2e8f0; } + +.hero-model { position: relative; min-height: 470px; overflow: hidden; border: 1px solid var(--observatory-border); border-radius: 2rem; background: linear-gradient(145deg, rgb(255 255 255 / .7), rgb(238 242 255 / .28)); box-shadow: inset 0 1px 0 rgb(255 255 255 / .65), 0 40px 90px rgb(30 64 175 / .12); isolation: isolate; } +.dark .hero-model { background: linear-gradient(145deg, rgb(15 23 42 / .72), rgb(30 27 75 / .4)); box-shadow: inset 0 1px 0 rgb(255 255 255 / .08), 0 40px 100px rgb(0 0 0 / .3); } +.hero-model::before { content: ''; position: absolute; inset: 0; z-index: -1; opacity: .35; background-image: linear-gradient(rgb(100 116 139 / .16) 1px, transparent 1px), linear-gradient(90deg, rgb(100 116 139 / .16) 1px, transparent 1px); background-size: 42px 42px; mask-image: linear-gradient(to bottom, black, transparent 90%); } +.hero-model canvas { display: block; width: 100%; height: 470px; cursor: grab; } +.hero-model canvas:active { cursor: grabbing; } +.hero-model__halo { position: absolute; left: 50%; top: 48%; z-index: -1; width: 75%; aspect-ratio: 1; transform: translate(-50%, -50%); border-radius: 50%; background: radial-gradient(circle, rgb(56 189 248 / .28), rgb(139 92 246 / .1) 45%, transparent 68%); filter: blur(12px); } +.hero-model__label { position: absolute; left: 1rem; top: 1rem; display: flex; align-items: center; gap: .45rem; color: #64748b; font-size: .66rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; } +.hero-model__label span { width: .4rem; height: .4rem; border-radius: 50%; background: #22d3ee; box-shadow: 0 0 10px #22d3ee; } +.hero-model__controls { position: absolute; right: 1rem; bottom: 1rem; display: flex; gap: .45rem; } +.hero-model__controls button { display: grid; width: 2.2rem; height: 2.2rem; place-items: center; color: #475569; border: 1px solid var(--observatory-border); border-radius: .7rem; background: var(--observatory-panel); backdrop-filter: blur(12px); } +.dark .hero-model__controls button { color: #cbd5e1; } +.hero-model__fallback { display: grid; height: 470px; place-items: center; color: #818cf8; font-size: 10rem; } + +.constellation-panel { display: grid; grid-template-columns: minmax(220px, .62fr) minmax(0, 1.38fr); gap: clamp(1.5rem, 5vw, 4rem); align-items: center; margin: 0 0 6rem; padding: clamp(1.5rem, 4vw, 3.5rem); border: 1px solid var(--observatory-border); border-radius: 2rem; background: var(--observatory-panel); box-shadow: 0 30px 80px rgb(15 23 42 / .08); backdrop-filter: blur(22px); } +.eyebrow { display: flex; align-items: center; gap: .5rem; color: #7c3aed; font-size: .68rem; font-weight: 800; letter-spacing: .13em; text-transform: uppercase; } +.dark .eyebrow { color: #c4b5fd; } +.constellation-copy h2 { margin-top: .85rem; color: #0f172a; font-size: clamp(2rem, 4vw, 3.3rem); font-weight: 800; line-height: 1; letter-spacing: -.045em; } +.dark .constellation-copy h2 { color: #f8fafc; } +.constellation-copy > p { margin-top: 1rem; color: #64748b; font-size: .93rem; line-height: 1.7; } +.dark .constellation-copy > p { color: #94a3b8; } +.constellation-detail { min-height: 8.5rem; margin-top: 1.6rem; padding: 1rem; border-left: 2px solid #8b5cf6; background: rgb(139 92 246 / .06); } +.constellation-detail span { color: #6d28d9; font-size: .72rem; font-weight: 800; text-transform: uppercase; letter-spacing: .1em; } +.dark .constellation-detail span { color: #c4b5fd; } +.constellation-detail p { margin-top: .4rem; color: #475569; font-size: .85rem; line-height: 1.55; } +.dark .constellation-detail p { color: #a8b4c7; } +.constellation-detail a { display: inline-flex; align-items: center; gap: .25rem; margin-top: .65rem; color: #0369a1; font-size: .78rem; font-weight: 750; } +.dark .constellation-detail a { color: #7dd3fc; } +.constellation-map { position: relative; aspect-ratio: 720 / 420; min-height: 300px; } +.constellation-map svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; } +.constellation-map line { stroke: #94a3b8; stroke-width: 1.2; opacity: .13; transition: opacity .25s, stroke-width .25s; } +.constellation-map line.is-active { stroke: url(#constellation-line); stroke-width: 2; opacity: .65; stroke-dasharray: 5 7; animation: constellation-flow 8s linear infinite; } +.constellation-node { position: absolute; display: flex; align-items: center; gap: .45rem; transform: translate(-50%, -50%); padding: .52rem .7rem; color: #334155; border: 1px solid var(--observatory-border); border-radius: 999px; background: rgb(255 255 255 / .86); box-shadow: 0 8px 25px rgb(15 23 42 / .1); font-size: clamp(.65rem, 1.3vw, .78rem); font-weight: 750; white-space: nowrap; transition: transform .2s, opacity .2s, box-shadow .2s; } +.dark .constellation-node { color: #dbeafe; background: rgb(15 23 42 / .9); } +.constellation-node:hover, .constellation-node.is-selected { transform: translate(-50%, -50%) scale(1.08); box-shadow: 0 10px 35px rgb(99 102 241 / .22); } +.constellation-node.is-muted { opacity: .35; } +.constellation-node > span { width: .48rem; height: .48rem; border-radius: 50%; background: #38bdf8; box-shadow: 0 0 10px currentColor; } +.constellation-node--optimization > span { background: #a78bfa; } +.constellation-node--application > span { background: #f472b6; } +.constellation-node--core > span { background: #22d3ee; } + +.simulation-panel { margin: 0 0 6rem; padding: clamp(1.5rem, 4vw, 3.5rem); overflow: hidden; color: #e2e8f0; border: 1px solid rgb(148 163 184 / .16); border-radius: 2rem; background: radial-gradient(circle at 86% 8%, rgb(139 92 246 / .28), transparent 28rem), linear-gradient(145deg, #081426, #111531 62%, #1c1641); box-shadow: 0 35px 90px rgb(15 23 42 / .2); } +.simulation-header { display: flex; align-items: end; justify-content: space-between; gap: 2rem; } +.simulation-header h2 { margin-top: .7rem; color: white; font-size: clamp(2rem, 4vw, 3.3rem); font-weight: 800; line-height: 1; letter-spacing: -.045em; } +.simulation-header > div > p:last-child { max-width: 35rem; margin-top: .9rem; color: #94a3b8; line-height: 1.65; } +.simulation-header label { display: grid; flex: 0 0 12rem; gap: .45rem; color: #94a3b8; font-size: .66rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; } +.simulation-header select { width: 100%; padding: .7rem .8rem; color: #e2e8f0; border: 1px solid rgb(148 163 184 / .2); border-radius: .7rem; background: rgb(15 23 42 / .75); font-size: .8rem; text-transform: none; letter-spacing: 0; } +.simulation-chart { margin: 2.5rem 0 1.8rem; padding: 1.2rem; border: 1px solid rgb(148 163 184 / .12); border-radius: 1rem; background: rgb(2 6 23 / .36); } +.simulation-chart svg { display: block; width: 100%; overflow: visible; } +.simulation-gridline { stroke: #64748b; stroke-width: 1; opacity: .18; } +.simulation-line { fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 4; filter: drop-shadow(0 0 7px currentColor); } +.simulation-line--incumbent { color: #22d3ee; stroke: #22d3ee; } +.simulation-line--bound { color: #a78bfa; stroke: #a78bfa; stroke-width: 2.5; stroke-dasharray: 8 7; } +.simulation-footer { display: flex; align-items: center; justify-content: space-between; gap: 1rem; } +.simulation-controls { display: flex; gap: .55rem; } +.simulation-controls button { display: inline-flex; align-items: center; gap: .4rem; padding: .65rem .85rem; color: #dbeafe; border: 1px solid rgb(148 163 184 / .2); border-radius: .65rem; background: rgb(30 41 59 / .62); font-size: .78rem; font-weight: 700; } +.simulation-controls button:first-child { color: #082f49; border-color: transparent; background: #67e8f9; } +.simulation-footer dl { display: flex; gap: 1.5rem; } +.simulation-footer dl div { min-width: 4.5rem; } +.simulation-footer dt { color: #64748b; font-size: .6rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; } +.simulation-footer dd { margin-top: .25rem; color: #f8fafc; font-size: .85rem; font-variant-numeric: tabular-nums; font-weight: 750; } + +@keyframes constellation-flow { to { stroke-dashoffset: -48; } } + +@media (max-width: 880px) { + .hero-observatory { grid-template-columns: 1fr; padding-top: 2rem; } + .hero-title { max-width: 12ch; } + .hero-model { min-height: 390px; } + .hero-model canvas, .hero-model__fallback { height: 390px; } + .constellation-panel { grid-template-columns: 1fr; } + .simulation-header, .simulation-footer { align-items: stretch; flex-direction: column; } + .simulation-header label { flex-basis: auto; } +} + +@media (max-width: 520px) { + .hero-title { font-size: clamp(3rem, 16vw, 4.5rem); } + .hero-metrics { width: 100%; justify-content: space-between; } + .hero-metrics div { min-width: 0; margin-right: .65rem; padding-right: .65rem; } + .constellation-panel { padding: 1.25rem; border-radius: 1.3rem; } + .constellation-map { min-height: 280px; } + .constellation-node { padding: .42rem .52rem; } + .simulation-panel { padding: 1.25rem; border-radius: 1.3rem; } + .simulation-footer dl { display: grid; grid-template-columns: 1fr 1fr; gap: .8rem; } +} + +@media (prefers-reduced-motion: reduce) { + html { scroll-behavior: auto; } + *, *::before, *::after { scroll-behavior: auto !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; } } /* Custom Scrollbar (from previous Gatsby setup) */ @@ -30,4 +185,4 @@ html { } ::-webkit-scrollbar-thumb:hover { background: #94a3b8; -} \ No newline at end of file +} diff --git a/app/layout.tsx b/app/layout.tsx index b75eb1d..2ce54eb 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -24,10 +24,8 @@ export default function RootLayout({ return ( - - {children} - + {children} ); -} \ No newline at end of file +} diff --git a/app/page.tsx b/app/page.tsx index 8e048ff..2ef3a86 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,9 +1,24 @@ import React from 'react'; import { PenTool, Calendar, Star, FileText, BookOpen, Code, ArrowRight } from 'lucide-react'; -import GlassCard from '../src/components/ui/GlassCard'; -import SectionHeading from '../src/components/ui/SectionHeading'; -import Badge from '../src/components/ui/Badge'; -import { BlogPost, Project } from '../src/types'; +import GlassCard from '../src/frameworks/react/components/ui/GlassCard'; +import SectionHeading from '../src/frameworks/react/components/ui/SectionHeading'; +import Badge from '../src/frameworks/react/components/ui/Badge'; +import { BlogPost, Project } from '../src/interfaces/types'; +import HeroModel from '../src/frameworks/react/components/models/HeroModel'; +import ResearchConstellation from '../src/frameworks/react/components/graph/ResearchConstellation'; +import ConvergenceSimulation from '../src/frameworks/react/components/routes/ConvergenceSimulation'; +import AudioSpectrum from '../src/frameworks/react/components/audio/AudioSpectrum'; +import ResearchShelf from '../src/frameworks/react/components/books/ResearchShelf'; +import FleetRouteCanvas from '../src/frameworks/react/components/canvas/FleetRouteCanvas'; +import PrototypeCard from '../src/frameworks/react/components/games/PrototypeCard'; +import MediaMosaic from '../src/frameworks/react/components/image/MediaMosaic'; +import FleetRouteMap from '../src/frameworks/react/components/maps/FleetRouteMap'; +import MediaReel from '../src/frameworks/react/components/video/MediaReel'; +import { StarfieldWrapper } from '../src/frameworks/astro/components/StarfieldWrapper'; +import { AureliaWrapper } from '../src/frameworks/aurelia/components/AureliaWrapper'; +import { WebGPUExperiment } from '../src/frameworks/react/components/canvas/WebGPUExperiment'; +import { WebXRExperiment } from '../src/frameworks/react/components/canvas/WebXRExperiment'; +import { GaussianSplatGallery } from '../src/frameworks/react/components/models/GaussianSplatGallery'; /** * Mock Data @@ -73,27 +88,64 @@ export default function Home() { return ( <> {/* Hero Section */} -
-
+
+
- Available for Research Collaboration + -

- Exploring the combination of Artificial Intelligence and Operations Research for Combinatorial Optimization. +

Scientist · Engineer · Educator

+

+ Learning systems for hard decisions.

-

- I'm a scientist and engineer focused on Deep Reinforcement Learning and Operations Research methods to solve Combinatorial Optimization problems. - Currently working as a researcher at INESC-ID and - teaching at IST. +

+ I combine deep reinforcement learning with operations research to solve combinatorial optimization problems—and turn the results into systems people can explore.

-
- - About Me + +
+
Focus
AI × OR
+
Research
INESC-ID
+
Teaching
IST
+
+
+ +
+ + + + + +
+

Field notes

Research, play, and the things that keep me curious.

+
+ + + + + + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
@@ -179,4 +231,4 @@ export default function Home() {
); -} \ No newline at end of file +} diff --git a/astro.config.mjs b/astro.config.mjs new file mode 100644 index 0000000..9603d87 --- /dev/null +++ b/astro.config.mjs @@ -0,0 +1,15 @@ +import { defineConfig } from 'astro/config'; + +// Astro sources live under src/frameworks/astro (pages/, components/, *.astro). +// Output is a static island consumed by the Next host via iframe. +export default defineConfig({ + srcDir: './src/frameworks/astro', + outDir: './public/astro-island', + publicDir: './astro-public', + base: '/github-pages/astro-island', + vite: { + css: { + postcss: {}, + }, + }, +}); diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 0000000..8d1f0ad --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1,60 @@ +# Website performance benchmark + +This directory contains a dependency-light benchmark for the statically exported site. It measures the assets that a visitor can request from `out/`, without requiring a hosted server, API, telemetry, or a third-party account. + +## Goals + +1. Catch accidental growth in the default route. +2. Record the transfer cost of representative content routes. +3. Make benchmark output reviewable in pull requests. +4. Leave room for browser-level Lighthouse/Cypress measurements later. + +## Quick start + +```bash +npm run benchmark:build +npm run benchmark +``` + +`benchmark:build` creates the production export. `benchmark` starts a temporary local static server, requests the configured routes, and writes JSON (`latest.json`) plus a reviewable Markdown summary (`latest.md`) under `benchmark/results/`. + +## What is measured + +- HTTP status and response time for `/github-pages/` and representative content routes. +- Response bytes and compressed bytes when the server provides them. +- Total export bytes and file count. +- JavaScript and CSS payload totals. +- Largest individual assets. +- A pass/warn/fail assessment against the roadmap budgets. + +The script intentionally does not claim to measure real-user LCP, INP, CLS, GPU frame time, memory, or accessibility. Those require a browser matrix and are tracked as follow-up work in the infrastructure roadmap. + +## Budgets + +The initial budgets are deliberately visible and editable in `measure.mjs`: + +| Budget | Default | Reason | +| --- | ---: | --- | +| Initial JavaScript | 200 kB | Protect the first interaction path. | +| Initial CSS | 80 kB | Keep typography and layout inexpensive. | +| Homepage transfer | 2 MB | Leave room for optional visual islands. | +| Route response | 3 MB | Keep content routes usable on slow connections. | +| Largest asset | 1 MB | Encourage responsive, licensed media. | + +Budgets are not a substitute for profiling. A route may exceed one budget for a justified media story, but the roadmap entry must record the reason, fallback, and measured alternative. + +## Repeatable procedure + +1. Use a clean production build. +2. Run the benchmark on the same machine and Node version used by CI. +3. Compare the generated summary with the previous commit. +4. Investigate changes greater than 10% before merging. +5. Record intentional changes in `docs/moon/CHANGELOG.md` and the relevant roadmap item. + +## Browser follow-up + +The next benchmark milestone will run Playwright or Cypress against Chromium, Firefox, and WebKit. It will collect navigation timing, Web Vitals, reduced-motion behavior, WebGL fallback behavior, and ten mount/unmount leak probes. Browser automation is kept separate so the static asset benchmark remains fast and works in constrained CI runners. + +## Result hygiene + +Generated results are ignored by Git. Keep one manually selected baseline in a release note or benchmark report when a roadmap gate is closed. Never commit private URLs, local file paths, cookies, user content, or machine identifiers. diff --git a/benchmark/measure.mjs b/benchmark/measure.mjs new file mode 100644 index 0000000..5061da1 --- /dev/null +++ b/benchmark/measure.mjs @@ -0,0 +1,87 @@ +#!/usr/bin/env node + +import { createServer } from 'node:http'; +import { readFile, readdir, stat } from 'node:fs/promises'; +import { existsSync } from 'node:fs'; +import { join, relative, extname } from 'node:path'; + +const root = process.cwd(); +const output = join(root, 'out'); +const results = join(root, 'benchmark', 'results'); +const port = Number(process.env.BENCHMARK_PORT ?? 4317); +const routes = ['/', '/content/about/', '/content/projects/', '/content/reports/', '/content/posts/']; +const budgets = { initialJs: 200_000, initialCss: 80_000, homepage: 2_000_000, route: 3_000_000, largest: 1_000_000 }; + +const formatBytes = (bytes) => `${(bytes / 1024).toFixed(1)} KiB`; + +function markdown(result) { + const check = (value) => value ? 'PASS' : 'WARN'; + const responseRows = result.responses.map((item) => `| \`${item.path}\` | ${item.status} | ${formatBytes(item.bytes)} | ${item.milliseconds} ms |`).join('\n'); + const assetRows = result.largest.slice(0, 5).map((item) => `| \`${item.path}\` | ${formatBytes(item.bytes)} |`).join('\n'); + return `# Static export benchmark\n\nGenerated: ${result.generatedAt} \nNode: ${result.node} \nOverall: **${result.passed ? 'PASS' : 'WARNINGS'}** (use \`BENCHMARK_STRICT=1\` to fail CI on a warning)\n\n## Totals\n\n| Metric | Value | Budget | Check |\n| --- | ---: | ---: | --- |\n| JavaScript | ${formatBytes(result.totals.javascriptBytes)} | ${formatBytes(result.budgets.initialJs)} | ${check(result.checks.initialJs)} |\n| CSS | ${formatBytes(result.totals.cssBytes)} | ${formatBytes(result.budgets.initialCss)} | ${check(result.checks.initialCss)} |\n| Export files | ${result.totals.files} | — | — |\n| Export bytes | ${formatBytes(result.totals.exportBytes)} | — | — |\n\n## Representative routes\n\n| Route | Status | Response | Time |\n| --- | ---: | ---: | ---: |\n${responseRows}\n\n## Largest assets\n\n| Path | Bytes |\n| --- | ---: |\n${assetRows}\n\n## Interpretation\n\nBudget warnings are optimization inputs, not evidence that content should be removed. Investigate eager dependencies, media derivatives, route-level chunks, and static fallback duplication. Record intentional exceptions in the infrastructure roadmap and the associated GitHub issue.\n`; +} + +async function filesIn(directory) { + const entries = await readdir(directory, { withFileTypes: true }); + const files = []; + for (const entry of entries) { + const path = join(directory, entry.name); + if (entry.isDirectory()) files.push(...await filesIn(path)); + else files.push(path); + } + return files; +} + +function contentType(path) { + return { '.html': 'text/html', '.css': 'text/css', '.js': 'text/javascript', '.json': 'application/json', '.svg': 'image/svg+xml' }[extname(path)] ?? 'application/octet-stream'; +} + +function server() { + return createServer(async (request, response) => { + const requested = decodeURIComponent((request.url ?? '/').split('?')[0]); + const candidates = requested.endsWith('/') + ? [join(output, requested, 'index.html'), join(output, requested.slice(0, -1) + '.html')] + : [join(output, requested), join(output, requested + '.html'), join(output, requested, 'index.html')]; + const path = candidates.find((candidate) => existsSync(candidate)); + if (!path) { response.writeHead(404); response.end('Not found'); return; } + try { + const body = await readFile(path); + response.writeHead(200, { 'content-type': contentType(path), 'content-length': body.byteLength }); + response.end(body); + } catch { + response.writeHead(404); + response.end('Not found'); + } + }); +} + +async function request(path) { + const started = performance.now(); + const response = await fetch(`http://127.0.0.1:${port}${path}`); + const body = await response.arrayBuffer(); + return { path, status: response.status, bytes: body.byteLength, milliseconds: Math.round(performance.now() - started) }; +} + +async function main() { + if (!existsSync(output)) throw new Error('out/ is missing; run npm run build first'); + const allFiles = await filesIn(output); + const sizes = await Promise.all(allFiles.map(async (path) => ({ path: relative(output, path), bytes: (await stat(path)).size }))); + const js = sizes.filter((file) => file.path.endsWith('.js')).reduce((sum, file) => sum + file.bytes, 0); + const css = sizes.filter((file) => file.path.endsWith('.css')).reduce((sum, file) => sum + file.bytes, 0); + const largest = [...sizes].sort((a, b) => b.bytes - a.bytes).slice(0, 10); + const http = server(); + await new Promise((resolve) => http.listen(port, '127.0.0.1', resolve)); + const responses = await Promise.all(routes.map(request)); + await new Promise((resolve) => http.close(resolve)); + const homepage = responses.find((item) => item.path === '/')?.bytes ?? 0; + const checks = { initialJs: js <= budgets.initialJs, initialCss: css <= budgets.initialCss, homepage: homepage <= budgets.homepage, routes: responses.every((item) => item.bytes <= budgets.route && item.status === 200), largest: largest[0]?.bytes <= budgets.largest }; + const result = { generatedAt: new Date().toISOString(), node: process.version, budgets, totals: { files: sizes.length, exportBytes: sizes.reduce((sum, file) => sum + file.bytes, 0), javascriptBytes: js, cssBytes: css }, responses, largest, checks, passed: Object.values(checks).every(Boolean) }; + await import('node:fs/promises').then(({ mkdir, writeFile }) => mkdir(results, { recursive: true }).then(async () => { + await writeFile(join(results, 'latest.json'), JSON.stringify(result, null, 2)); + await writeFile(join(results, 'latest.md'), markdown(result)); + })); + console.log(JSON.stringify(result, null, 2)); + if (!result.passed && process.env.BENCHMARK_STRICT === '1') process.exitCode = 2; +} + +main().catch((error) => { console.error(error.message); process.exitCode = 1; }); diff --git a/cypress/screenshots/navigation.cy.js/Navigation -- should display the Math Curriculum card on the Other page (failed).png b/cypress/screenshots/navigation.cy.js/Navigation -- should display the Math Curriculum card on the Other page (failed).png deleted file mode 100644 index e00c76a..0000000 Binary files a/cypress/screenshots/navigation.cy.js/Navigation -- should display the Math Curriculum card on the Other page (failed).png and /dev/null differ diff --git a/cypress/screenshots/navigation.cy.js/Navigation -- should navigate to valid pages from the SidebarHeader (failed).png b/cypress/screenshots/navigation.cy.js/Navigation -- should navigate to valid pages from the SidebarHeader (failed).png deleted file mode 100644 index 4a63616..0000000 Binary files a/cypress/screenshots/navigation.cy.js/Navigation -- should navigate to valid pages from the SidebarHeader (failed).png and /dev/null differ diff --git a/cypress/screenshots/other.cy.js/Other Section -- should navigate to the Other section and load the Math Curriculum (failed).png b/cypress/screenshots/other.cy.js/Other Section -- should navigate to the Other section and load the Math Curriculum (failed).png deleted file mode 100644 index 89159c5..0000000 Binary files a/cypress/screenshots/other.cy.js/Other Section -- should navigate to the Other section and load the Math Curriculum (failed).png and /dev/null differ diff --git a/cypress/screenshots/other.cy.js/Other Section -- should switch tabs to Course Explorer (failed).png b/cypress/screenshots/other.cy.js/Other Section -- should switch tabs to Course Explorer (failed).png deleted file mode 100644 index b286b99..0000000 Binary files a/cypress/screenshots/other.cy.js/Other Section -- should switch tabs to Course Explorer (failed).png and /dev/null differ diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..0a531b0 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,379 @@ +# Architecture + +**Status:** Active · **Revision:** R3 · **Updated:** 2026-08-08 + +This document describes the executable architecture of `github-pages`, ACFHarbinger's static personal website and research observatory. It is intentionally implementation-oriented: a contributor should be able to locate a module, understand its ownership, run the relevant test, and choose the correct progressive-enhancement boundary without consulting tribal knowledge. + +## Contents + +- [Goals and non-goals](#goals-and-non-goals) +- [System context](#system-context) +- [Build and request flows](#build-and-request-flows) +- [Repository map](#repository-map) +- [Layer contracts](#layer-contracts) +- [Content pipeline](#content-pipeline) +- [Interactive experience architecture](#interactive-experience-architecture) +- [Simulation and Aurelia boundaries](#simulation-and-aurelia-boundaries) +- [Redux state architecture](#redux-state-architecture) +- [Rendering tiers and capability policy](#rendering-tiers-and-capability-policy) +- [Performance and benchmark architecture](#performance-and-benchmark-architecture) +- [Accessibility and resilience](#accessibility-and-resilience) +- [Security, privacy, and licensing](#security-privacy-and-licensing) +- [Testing architecture](#testing-architecture) +- [TypeScript and React excerpts](#typescript-and-react-excerpts) +- [Decision records](#decision-records) +- [Change checklist](#change-checklist) + +## Goals and non-goals + +### Goals + +1. Preserve useful content when JavaScript, WebGL, WebGPU, audio, or a third-party asset fails. +2. Make research claims inspectable through citations, units, assumptions, provenance, and data equivalents. +3. Keep route code, domain components, simulation engines, and cross-route state independently testable. +4. Ship a small static export first, then hydrate optional visual islands after intent or visibility. +5. Keep experiments relevant to waste-fleet optimization, machine learning, game development, media, and technical/political history. + +### Non-goals + +- A runtime API, database, authentication system, or hidden analytics collector. +- Treating an illustrative simulation as a production solver or scientific publication. +- Making WebGPU, WebXR, 3D, audio, or continuous animation a prerequisite for reading. +- Putting every interactive feature into a single generic component directory. + +## System context + +```mermaid +flowchart LR + Author[Markdown / front matter / research notes] --> Loader[lib content loader] + Loader --> Build[Next.js static build] + Build --> Export[out/ static export] + Export --> Pages[GitHub Pages] + Visitor[Visitor browser] --> Pages + Visitor --> Islands[Optional React visual islands] + Islands --> Redux[Redux shell state] + Islands --> Sim[Framework-neutral simulations] + Sim --> Workers[Future Web Workers] + Research[notebooks + uv] -. produces reviewed findings .-> Author + Benchmark[benchmark/measure.mjs] --> Export +``` + +The dashed research edge is deliberately one-way. Notebooks can produce a report, but the production site never imports Python, secrets, a database, or a notebook runtime. + +## Build and request flows + +### Static build + +```mermaid +sequenceDiagram + participant N as npm + participant Next as Next build + participant L as lib/content + participant A as app routes + participant O as out/ + N->>Next: npm run build + Next->>L: load section files + L-->>A: typed front matter + HTML + A->>Next: render pages and layouts + Next->>O: write static HTML/CSS/JS/assets + N->>O: postbuild creates out/github-pages symlink +``` + +### Browser navigation + +```mermaid +sequenceDiagram + participant B as Browser + participant H as Static HTML + participant S as Client shell + participant I as Optional island + B->>H: request route + H-->>B: semantic content + fallback + B->>S: hydrate header/sidebar/theme + S->>I: hydrate after visibility or intent + I-->>S: serializable interaction event + S-->>B: focus, text metrics, or visual update +``` + +The fallback is the product. Hydration improves direct manipulation; it must not be the only way to discover a report or project. + +## Repository map + +```text +app/ + layout.tsx # document metadata and static shell boundary + page.tsx # observatory landing page + content/
/ # route pages and content indexes +src/ + aurelia/ # optional Aurelia islands; never required globally + components/ + audio/ books/ canvas/ games/ graph/ image/ + maps/ models/ routes/ video/ # focused domain components + layout/ ui/ # shell and shared presentation primitives + configs/ constants/ enums/ hooks/ interfaces/ + context/ redux/ routes/ types/ utils/ + simulations/ + repository/ # contracts, serializable types, dataset lookup + scenarios/ # immutable presets and fixtures + generator/ # deterministic computations + context/ # lifecycle controllers +lib/ # build-time Markdown/front-matter utilities +docs/ + ARCHITECTURE.md # this document + moon/ROADMAP.md # product roadmap and evidence gates + moon/roadmaps/ # issue-ready workstream roadmaps + moon/research/ # research reports and source registers +benchmark/ # production export performance harness +infra/ # optional self-hosting / alt-deploy (not the default GitHub Pages path) + global/ # external public-facing deploy & host configs + ansible/ docker/ helm/ k8s/ terraform/ + private/ # internal developer-only tooling + webpack/ wordpress/ + cloud/ # managed cloud static hosts (AWS/Azure/Firebase/Serverless) + server/ # standalone nginx and Envoy reverse-proxy configs + nginx/ proxy/ +notebooks/ # independent Python/uv research workspace +public/ # licensed/static browser assets +test/ + unit/ integration/ cypress/ # fast tests, network tests, browser tests +``` + +## Layer contracts + +| Layer | Owns | May import | Must not own | +| --- | --- | --- | --- | +| `app/` | routes, metadata, composition | `src`, `lib` | algorithms, browser-only globals at build time | +| `lib/` | front matter and Markdown parsing | Node parsing libraries | React state, network calls | +| `components/ui` | visual primitives and semantics | React, shared interfaces | route-specific business decisions | +| `components/` | one focused interactive or media surface | UI primitives, typed interfaces | global Redux for hover/cursor state | +| `simulations/generator` | deterministic calculations | repository contracts | DOM, React, wall clock, random global | +| `simulations/context` | lifecycle and orchestration | generator, repository | JSX and CSS | +| `redux` | cross-route experience preferences | serializable actions/reducers | simulation frames, media buffers, refs | +| `notebooks` | exploratory analysis | Python/uv dependencies | production imports | +| `benchmark` | build artifact measurements | Node standard library | user data, telemetry, secrets | +| `infra/global` | optional public deploy/host alternatives | container/IaC tooling | site runtime imports | +| `infra/private` | developer-only infra experiments | local build tooling | production deploy path | +| `infra/cloud` | managed cloud static-host configs | cloud CLIs / pipelines | site runtime imports | +| `infra/server` | nginx / Envoy reverse-proxy configs | reverse-proxy daemons | app source imports | + +## Content pipeline + +Content is authored in section-specific directories under `app/content`. A content file has front matter, Markdown body, and optional local assets. The loader validates the section and slug, parses Markdown at build time, and returns a typed record. Invalid front matter fails the build rather than producing a silently incomplete page. + +```mermaid +flowchart TD + File[Markdown file] --> Front[gray-matter front matter] + Front --> Validate[section schema validation] + Validate --> Remark[remark + remark-html] + Remark --> Record[typed ContentRecord] + Record --> Index[section index] + Record --> Detail[static detail route] + Record --> Meta[title / description / OG metadata] +``` + +Content links to research evidence but does not embed unreviewed notebook output. Reports distinguish measured results, illustrative fixtures, and hypotheses. + +## Interactive experience architecture + +Every interactive item follows the same decomposition: + +1. **Claim:** a visible heading and one-sentence visitor question. +2. **Data contract:** a small immutable TypeScript type with units and provenance. +3. **Fallback:** list, table, SVG, still image, or ordinary links. +4. **Controller:** local state or a simulation controller, never a hidden singleton. +5. **Enhancement:** canvas, Three.js, Web Audio, map, or future WebGPU. +6. **Teardown:** event listeners, animation frames, contexts, workers, and object URLs released. +7. **Evidence:** test fixture, benchmark result, and roadmap ID. + +```mermaid +flowchart LR + Claim --> Contract --> Fallback --> Controller --> Enhancement + Enhancement --> Teardown + Controller --> Evidence[tests + benchmark + provenance] + Fallback --> Evidence +``` + +Domain placement is intentional: a route animation belongs in `components/routes`, a spectrum in `audio`, a model in `models`, a reading shelf in `books`, and so on. Shared behavior belongs in hooks or utilities only when two domains have the same contract. + +## Simulation and Aurelia boundaries + +The simulation subsystem is framework-neutral. `repository/types.ts` contains serializable contracts; `scenarios/scenarios.ts` contains seeded fixtures; `generator` produces deterministic points; `context` exposes play/pause/step/reset and lifecycle status. React renders the controller through a client component. Aurelia mounts only inside an explicitly isolated island. + +```mermaid +flowchart TD + Scenario[src/simulations/scenarios] --> Contract[src/simulations/repository] + Contract --> Generator[src/simulations/generator] + Generator --> Controller[src/simulations/context] + Controller --> React[React adapter] + Controller --> Aurelia[Aurelia island adapter] + Controller --> Worker[future worker adapter] +``` + +React and Aurelia must return the same snapshot for the same scenario and seed. A worker message is versioned, carries a request ID, and cannot overwrite a newer request. + +## Redux state architecture + +Redux is deliberately small. It currently stores theme, quality preference, active simulation, and active media because those can cross routes or independent surfaces. Hover, drag position, playback cursor, animation frame, form draft, and DOM refs remain local. + +```mermaid +flowchart LR + Header -->|theme action| Store[Redux store] + Convergence -->|active simulation| Store + Media -->|active media| Store + Store --> ClientLayout[Client shell selectors] + Store -. no frames or refs .-> Local[component-local state] +``` + +Persistence is browser-guarded. Static rendering sees the default state; hydration may restore a preference without changing the meaning of the content. + +## Rendering tiers and capability policy + +| Tier | Trigger | Rendering | Required equivalent | +| --- | --- | --- | --- | +| Static | no JS/WebGL, crawler, failure | HTML, SVG, table, still | full claim and controls as links | +| Reduced | reduced motion, low memory, coarse pointer | event-driven canvas, lower DPR, no post-processing | text metrics and keyboard path | +| Full | capable device and opt-in preference | Three.js/audio/map/WebGPU enhancement | same data and reset controls | + +Capability detection is advisory. A device can report support and still fail allocation; every initializer catches failure and returns the fallback. Experimental WebGPU, WebXR, and splats are excluded from the default route payload until the benchmark and device matrix justify promotion. + +## Performance and benchmark architecture + +`benchmark/measure.mjs` measures the built artifact and representative HTTP responses. It does not use a browser and therefore cannot replace Lighthouse. Its output is a deterministic budget signal for pull requests. + +```mermaid +flowchart TD + Build[npm run benchmark:build] --> Export[out/] + Export --> Files[file count / JS / CSS / largest assets] + Export --> Server[ephemeral localhost server] + Server --> Routes[representative route requests] + Files --> Checks[budget checks] + Routes --> Checks + Checks --> JSON[benchmark/results/latest.json] +``` + +Default budgets are 200 kB JavaScript, 80 kB CSS, 2 MB homepage transfer, 3 MB per route response, and 1 MB largest asset. If a roadmap item intentionally exceeds a budget, its document must record the user-facing value, static fallback, and mitigation. + +## Accessibility and resilience + +- Headings, landmarks, links, tables, and form labels exist before enhancement. +- Every visual encoding has a textual summary; color is never the only category. +- Focus is visible; keyboard controls mirror pointer controls; live regions avoid noisy frame-by-frame announcements. +- Reduced motion disables continuous animation and audio-reactive effects. +- Audio never autoplays; local media remains local; object URLs and audio contexts are released. +- 3D/model surfaces expose reset, pause, loading, error, and static poster states. +- Context loss, unsupported APIs, malformed data, timeout, cancellation, and worker crashes preserve a visitor-readable result. + +## Security, privacy, and licensing + +The static export has no server secrets. Do not add API keys to client code, fetch private research data at runtime, or upload local audio/images. Every external or generated asset needs a license/source entry beside the feature. Research data should be minimized, anonymized, and accompanied by a limitation statement. + +## Testing architecture + +| Test | Scope | Typical command | +| --- | --- | --- | +| Unit | pure utilities, reducers, generators | `npm run test:unit` | +| Integration | shell composition and mocked network | `npm run test:integration` | +| Browser smoke | route rendering and theme path | `npm run cypress:smoke` | +| E2E | user journeys against served export | `npm run cypress:e2e` | +| Static benchmark | export size and response budgets | `npm run benchmark:build && npm run benchmark` | + +New interactive components require a deterministic fixture, fallback assertion, keyboard interaction test, and teardown test. Graphics snapshots should test data/selection semantics rather than brittle pixels unless a rendering regression is the explicit goal. + +## TypeScript and React excerpts + +### Serializable simulation contract + +```ts +export interface SimulationScenario { + id: string; + seed: number; + iterations: number; + initialCost: number; + convergenceRate: number; +} + +export interface SimulationSnapshot { + scenarioId: string; + step: number; + points: ReadonlyArray<{ step: number; cost: number }>; + status: 'idle' | 'running' | 'paused' | 'complete' | 'error'; +} +``` + +The contract contains no `Date`, `Error`, class instance, DOM node, function, or framework-specific object so it can cross React, Aurelia, tests, URL serialization, and a future worker. + +### Capability-aware React island + +```tsx +'use client'; + +export function ProgressiveSurface({ fallback }: { fallback: React.ReactNode }) { + const [tier, setTier] = useState<'static' | 'reduced' | 'full'>('static'); + useEffect(() => { + const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + setTier(reduced || !('requestAnimationFrame' in window) ? 'reduced' : 'full'); + }, []); + if (tier === 'static') return <>{fallback}; + return
{tier === 'full' ? : }
; +} +``` + +The real implementation should also observe visibility, capability failures, context loss, and teardown. The excerpt demonstrates the policy boundary: the fallback is an explicit input, not an afterthought. + +### Redux action boundary + +```ts +export type ExperienceAction = + | { type: 'experience/themeChanged'; theme: 'light' | 'dark' } + | { type: 'experience/qualityChanged'; quality: 'static' | 'reduced' | 'full' } + | { type: 'experience/simulationActivated'; id: string | null }; +``` + +Actions remain serializable and domain-neutral. A route cursor is not promoted to Redux merely because it is convenient. + +### Worker protocol shape + +```ts +export type WorkerMessage = + | { version: 1; requestId: string; kind: 'start'; payload: T } + | { version: 1; requestId: string; kind: 'cancel' } + | { version: 1; requestId: string; kind: 'progress'; fraction: number } + | { version: 1; requestId: string; kind: 'result'; payload: unknown } + | { version: 1; requestId: string; kind: 'error'; message: string }; +``` + +Unknown versions fail safely. The controller checks the request ID before applying a result, which prevents stale work from replacing a newer scenario. + +## Decision records + +Architecture decisions live in [`docs/adr/`](adr/). Create an ADR before changing rendering ownership, adding a runtime backend, introducing a persistent canvas, changing the content schema, adding a model/audio format, or moving cross-route state into Redux. + +Current architectural decisions: + +1. Next.js static export is the deployment contract (default: GitHub Pages via `.github/workflows/deploy.yml`; optional self-host/cloud alternatives under `infra/global/`). +2. Markdown is parsed at build time. +3. Simulation algorithms are framework-neutral. +4. Domain components are separated by interaction/media type. +5. Progressive enhancement preserves a static equivalent. +6. Benchmarks are artifact-first, with browser profiling as a separate tier. + +## Change checklist + +Before opening a pull request: + +- [ ] Identify the roadmap ID and visitor question. +- [ ] Choose the owning directory and explain why it is not a shared catch-all. +- [ ] Define serializable input/output contracts and units. +- [ ] Add semantic fallback, keyboard path, reduced-motion path, and error state. +- [ ] Release listeners, frames, contexts, workers, object URLs, and subscriptions. +- [ ] Add unit/integration/browser coverage appropriate to the risk. +- [ ] Run lint, typecheck, tests, build, and benchmark. +- [ ] Update changelog, roadmap status, architecture notes, and GitHub issue. +- [ ] Record measured bundle/performance changes and any new license. + +## Document history + +| Date | Revision | Change | +| --- | --- | --- | +| 2026-08-09 | R3.1 | Documented `infra/global` vs `infra/private` layout after consolidating `cloud/` under infra. | +| 2026-08-08 | R3 | Replaced the short overview with system flows, contracts, diagrams, excerpts, benchmark architecture, and contributor gates. | diff --git a/docs/BENCHMARKS.md b/docs/BENCHMARKS.md new file mode 100644 index 0000000..c51ceff --- /dev/null +++ b/docs/BENCHMARKS.md @@ -0,0 +1,8 @@ +# Benchmarks + +| Concern | Tool | Notes | +| --- | --- | --- | +| Build output size | `npm run build` output | Next.js prints per-route bundle sizes; watch for regressions on large content additions | +| Page performance | [Lighthouse](https://developer.chrome.com/docs/lighthouse/) (`npx lighthouse `) | Run via [`.github/workflows/benchmark.yml`](../.github/workflows/benchmark.yml) against the static export, or manually against the deployed site | + +> **TODO:** Track Lighthouse scores over time (store/compare reports) if this becomes worth tracking beyond a point-in-time check. diff --git a/docs/DEPENDENCIES.md b/docs/DEPENDENCIES.md new file mode 100644 index 0000000..83e362b --- /dev/null +++ b/docs/DEPENDENCIES.md @@ -0,0 +1,13 @@ +# Dependencies + +| Module | Location | Manifest | Package Manager | +| --- | --- | --- | --- | +| Site | repo root (`app/`, `src/`, `lib/`) | `package.json` | `npm` | +| Notebooks | `notebooks/` | `pyproject.toml` | `uv` | +| Git automation | `git/` | `pyproject.toml` | `uv` | +| Optional self-hosting | `infra/global/` (docker, k8s, helm, terraform, ansible) | per-tool configs (no shared lockfile) | Docker / kubectl / helm / terraform / ansible | +| Managed cloud hosts | `infra/cloud/` (aws, azure-pipelines, firebase, serverless) | per-tool configs | AWS / Azure / Firebase / Serverless CLIs | +| Reverse proxies | `infra/server/` (nginx, proxy/Envoy) | config files only | nginx / envoy | +| Dev-only infra | `infra/private/` (webpack, wordpress) | none (sample configs) | — | + +See [`DEPENDENCY_POLICY.md`](DEPENDENCY_POLICY.md) for policies on adding, pinning, and upgrading dependencies. diff --git a/docs/DEPENDENCY_POLICY.md b/docs/DEPENDENCY_POLICY.md new file mode 100644 index 0000000..48c9ac8 --- /dev/null +++ b/docs/DEPENDENCY_POLICY.md @@ -0,0 +1,8 @@ +# Dependency Policy + +1. **Prefer the standard library / framework built-ins** before adding a new dependency. +2. **Pin exact or compatible-release versions** in `package.json`/`pyproject.toml`; never depend on a floating `latest`. +3. **One dependency, one purpose.** Don't add a second library that overlaps an existing one's functionality without removing the old one. +4. **License check.** New dependencies must use a license compatible with this repository's [AGPL-3.0 license](../LICENSE) (GPL-compatible, MIT, Apache-2.0, BSD are fine; proprietary or source-available-only licenses are not). +5. **Security.** Dependabot and the [security workflow](../.github/workflows/security.yml) run `npm audit`/`pip-audit` automatically; high-severity findings block merge. +6. **Major version bumps** (Next.js, React especially) get a dedicated PR, reviewed separately from content/feature work. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md new file mode 100644 index 0000000..6e9ca1c --- /dev/null +++ b/docs/DEVELOPMENT.md @@ -0,0 +1,54 @@ +# Development Guide + +## Prerequisites + +- **Git**, [Node.js](https://nodejs.org/) >= 20, `npm` +- **Notebooks (optional):** `python` (>= 3.11) + [`uv`](https://github.com/astral-sh/uv) +- `pre-commit` (`pip install pre-commit && pre-commit install`) + +## Local Setup + +```bash +git clone https://github.com/ACFHarbinger/github-pages.git +cd github-pages +npm install +npm run dev +``` + +The dev server runs at `http://localhost:3000/github-pages` (the `basePath` matches the GitHub Pages deployment path). + +## Notebooks + +```bash +cd notebooks +uv sync --extra dev +uv run jupyter lab +``` + +## Module Execution & Development + +| Task | Command | +| --- | --- | +| Dev server | `npm run dev` | +| Static export build | `npm run build` | +| Serve the export locally | `npm start` | +| Lint | `npm run lint` | +| Unit + integration tests | `npm test` / `npm run test:watch` | +| E2E / smoke tests | `npm run cypress:open` / `npm run cypress:run` / `npm run cypress:smoke` | + +## Optional self-hosting (`infra/`) + +Default deployment is GitHub Pages. Alternative container/cloud tooling lives under [`infra/`](../infra/README.md): + +| Task | Command | +| --- | --- | +| Docker Compose (nginx serving `out/`) | `docker compose -f infra/global/docker/docker-compose.yml up --build` | +| Kubernetes (dev overlay) | `kubectl apply -k infra/global/k8s/overlays/dev` | +| Helm install | `helm install github-pages infra/global/helm/github-pages -f infra/global/helm/github-pages/values.yaml` | +| Terraform (ECR) | `cd infra/global/terraform && terraform init && terraform plan -var-file=environments/dev.tfvars` | +| Ansible (plain host) | `cd infra/global/ansible && ansible-playbook -i inventory/hosts.ini playbook.yml` | +| Serverless (S3 static) | `npm run build && npx serverless client deploy --config infra/cloud/serverless/serverless.yml` | +| AWS CloudFormation template | `infra/cloud/aws/cfn-template.yaml` (see stack Outputs for the post-build sync command) | +| Azure Static Web Apps pipeline | `infra/cloud/azure-pipelines/azure-pipelines.yml` (point Azure DevOps at this path) | +| Standalone nginx (static `out/`) | See `infra/server/nginx/README.md` | +| Envoy reverse proxy | See `infra/server/proxy/README.md` | diff --git a/docs/DOCUMENTATION_STANDARDS.md b/docs/DOCUMENTATION_STANDARDS.md new file mode 100644 index 0000000..cbdca99 --- /dev/null +++ b/docs/DOCUMENTATION_STANDARDS.md @@ -0,0 +1,8 @@ +# Documentation Standards + +- **Doc-comments**: TSDoc for TypeScript/React, Google-style docstrings for Python (`notebooks/`, `git/scripts/`). Every exported function/component gets one when its behavior isn't obvious from its signature. +- **Markdown docs** live under `docs/`; each page starts with a one-paragraph summary before any headings. +- **Diagrams**: a simple Markdown table or a small Mermaid diagram inline beats an external diagramming tool for a site this size. +- **Code examples** in docs must be runnable against the current codebase; stale examples are worse than no examples. +- **ADRs** (`docs/adr/`) record decisions, not designs-in-progress — write one only once a decision is made; never edit a merged ADR, supersede it with a new one instead. +- **Inclusive language**: avoid ableist/exclusionary phrasing; the markdown link-checker in `.pre-commit-config.yaml` and [`.github/workflows/docs.yml`](../.github/workflows/docs.yml) also catch dead links. diff --git a/docs/GLOSSARY.md b/docs/GLOSSARY.md new file mode 100644 index 0000000..20724c2 --- /dev/null +++ b/docs/GLOSSARY.md @@ -0,0 +1,9 @@ +# Glossary + +| Term | Meaning | +| --- | --- | +| ADR | Architecture Decision Record — a short document capturing a significant, hard-to-reverse technical decision and its rationale. See [`docs/adr/`](adr/). | +| Static export | Next.js `output: 'export'` mode: the app builds to plain HTML/CSS/JS in `out/` with no server runtime, suitable for GitHub Pages. | +| Front-matter | YAML metadata block at the top of a Markdown content file (title, date, tags, etc.), parsed via `gray-matter`. | +| Content section | One of the top-level folders under `app/content/` (`posts`, `reports`, `projects`, `tools`, `media`, `about`, `other`). | +| Notebook | A Jupyter notebook under `notebooks/`, used for exploratory analysis backing a report; not part of the site build. | diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100644 index 0000000..6136178 --- /dev/null +++ b/docs/TESTING.md @@ -0,0 +1,20 @@ +# Testing Guide + +| Layer | Framework | Command | +| --- | --- | --- | +| Unit (components/logic) | Vitest + Testing Library | `npm run test:unit` | +| Integration (composed components) | Vitest + Testing Library + MSW | `npm run test:integration` | +| E2E (user flows) | Cypress | `npm run cypress:e2e` (needs `npm run build && npm start`, or `npm run dev`, running) | +| Smoke (fast sanity check) | Cypress | `npm run cypress:smoke` | + +Tests live under `test/unit/` (mirroring `src/components/`), `test/integration/`, and `test/cypress/` (`e2e/` — one spec per content section — plus `smoke/`). + +## Coverage + +`npx vitest run --coverage` reports coverage (requires `@vitest/coverage-v8`, not currently installed). There is no dedicated coverage service configured for this repo today. + +## Writing Tests + +- New components get a unit test in `test/unit/components/` covering render, interaction, and at least one empty/error state. +- New multi-component interactions (e.g. anything wiring into `ClientLayoutWrapper`) get an integration test in `test/integration/`. Mock network calls with MSW (`test/integration/mocks/handlers.ts`) rather than real `fetch`. +- New or changed user-facing flows get a Cypress spec under `test/cypress/e2e/`, scoped to one content section per file. Build-breaking regressions should also be catchable by `test/cypress/smoke/`. diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md new file mode 100644 index 0000000..05529ea --- /dev/null +++ b/docs/TROUBLESHOOTING.md @@ -0,0 +1,28 @@ +# Troubleshooting + +## `npm run build` fails + +This site is a static export (`output: 'export'` in `next.config.js`). Common causes: +- A component uses a server-only or Node-only API from client code. +- A page reaches for `window`/`document` outside a `useEffect`/client component. +- An API route was added — static export doesn't support them. + +## Fresh clone: TypeScript/ESLint errors on `npm install` + +Delete `node_modules` and re-run `npm ci` (not `npm install`) to get the exact locked versions from `package-lock.json`. + +## Cypress can't reach the site + +`test/cypress/cypress.config.js` points `baseUrl` at `http://localhost:3000/github-pages` — the `basePath` is part of the URL. Make sure `npm run dev`, or `npm start` after `npm run build`, is actually running before `npm run cypress:run`/`npm run cypress:smoke`. Note `npm start` alone (serving `out/` with `serve`) won't respond under `/github-pages` unless `npm run build` has run first — its `postbuild` script creates a self-referencing `out/github-pages` symlink so the static export answers at that path locally, matching how GitHub Pages serves it. + +## Cypress reports "no spec files were found" + +Cypress resolves `--spec` and `specPattern` relative to the current working directory, not the `--config-file`/`--project` flag. Always run Cypress commands from inside `test/cypress/` (the `npm run cypress:*` scripts already `cd` there first) rather than passing `--config-file` from the repo root. + +## Notebooks: `uv sync` can't find dependencies + +Run `uv sync --extra dev` from inside `notebooks/`, not the repo root — it's a separate workspace member with its own `pyproject.toml`. + +## Site renders but assets 404 on GitHub Pages + +Check `next.config.js`'s `basePath`/`NEXT_PUBLIC_BASE_PATH` still match the repository name (`/github-pages`) — a repo rename requires updating both. diff --git a/docs/adr/0001-record-architecture-decisions.md b/docs/adr/0001-record-architecture-decisions.md new file mode 100644 index 0000000..1cb126a --- /dev/null +++ b/docs/adr/0001-record-architecture-decisions.md @@ -0,0 +1,20 @@ +# 1. Record architecture decisions + +Date: 2026-07-30 + +## Status + +Accepted + +## Context + +We need a lightweight way to record significant, hard-to-reverse technical decisions so future contributors (human or LLM agent) understand *why* the system looks the way it does, not just *what* it does. + +## Decision + +We will use Architecture Decision Records (ADRs), one per decision, numbered sequentially under `docs/adr/`, following the format described by [Michael Nygard](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions). + +## Consequences + +- Every ADR is immutable once accepted; a reversal gets a new ADR that supersedes it, rather than editing history. +- `docs/ARCHITECTURE.md` links to relevant ADRs instead of re-explaining their reasoning. diff --git a/docs/adr/0002-graphics-renderer-lifecycle.md b/docs/adr/0002-graphics-renderer-lifecycle.md new file mode 100644 index 0000000..ac9c4dc --- /dev/null +++ b/docs/adr/0002-graphics-renderer-lifecycle.md @@ -0,0 +1,39 @@ +# 2. Graphics Renderer Lifecycle + +Date: 2026-08-08 + +## Status + +Accepted + +## Context + +The repository roadmap demands rich interactive graphics, including 3D model viewers and geospatial visualizations, while operating under strict performance and bundle size constraints (e.g., initial route JS ≤ 200 kB gzip, LCP ≤ 2.5s). We needed to decide on a lifecycle strategy for WebGL/Three.js renderers. + +The primary options were: +1. **Persistent Singleton Canvas:** A single global `` element mounted high in the Next.js layout tree, acting as a portal for all graphics across different routes. +2. **Isolated Lazy Islands:** Individual components (e.g., `HeroModel`) that instantiate their own `` and WebGL context on demand, dynamically importing the required libraries only when they enter the viewport. + +### Evaluation Criteria +- **Memory & Context Count:** Browsers strictly limit the number of concurrent WebGL contexts (usually 8-16). A singleton canvas guarantees only one context is ever created. Isolated islands risk hitting the context limit if multiple canvases exist simultaneously without proper lifecycle management. +- **Route Persistence:** A singleton canvas can maintain state and smooth transitions across Next.js route changes. Islands must be torn down and rebuilt when routes change. +- **First Interaction Cost & Chunk Size:** A singleton canvas requires loading the Three.js library in the global bundle, negatively impacting initial load times for all users, even those who might not scroll to the interactive element. Islands can be code-split and loaded lazily. + +## Decision + +We will use **Isolated Lazy Islands** with strict lifecycle management. + +We will not use a persistent singleton canvas because it violates our strict bundle budget and capabilities-gating policy ("explain before embellishing"). Loading graphics engines globally hurts the initial load time for visitors who may only want to read text. + +To mitigate the context limit and memory issues inherent to isolated islands, every graphics component *must* adhere to the lifecycle implemented in `HeroModel` (IF2): +- **Lazy Initialization:** Engines like Three.js are dynamically imported via `IntersectionObserver` only when the component is visible. +- **Visibility Suspension:** Rendering loops must halt when `document.visibilityState === 'hidden'` or the component scrolls out of view. +- **Rigorous Teardown:** The component must explicitly dispose of `WebGLRenderer`, `geometries`, and `materials`, and remove event listeners on unmount. +- **Context Loss Handling:** All components must handle `webglcontextlost` gracefully and provide a static fallback. + +## Consequences + +- **Positive:** Initial route bundles remain small. Users on constrained devices or low-bandwidth connections are not penalized by heavy graphics engines they haven't requested. +- **Positive:** Components remain modular, isolated, and domain-specific (IF13). +- **Negative:** We lose the ability to animate seamlessly across route transitions. +- **Negative:** Developer overhead increases. Every new graphics component must duplicate boilerplate for visibility checking and WebGL resource disposal to avoid memory leaks and context exhaustion. diff --git a/docs/adr/0003-geospatial-renderer.md b/docs/adr/0003-geospatial-renderer.md new file mode 100644 index 0000000..cbf4c44 --- /dev/null +++ b/docs/adr/0003-geospatial-renderer.md @@ -0,0 +1,19 @@ +# ADR 0003: Geospatial and Graph Renderer Strategy + +## Status +Accepted + +## Context +We need a strategy for rendering geospatial and graph data visualizations within the site. As the dataset sizes can vary significantly, we must choose a rendering technology that provides the best balance between performance, bundle size, and ease of implementation. WebGL-based solutions like Deck.gl offer extreme performance for large datasets but come with a steep learning curve, large bundle sizes, and complexity. SVG and Canvas 2D are native web technologies that are easier to implement and maintain but have performance limits. + +## Decision +We will adopt a progressive enhancement strategy for geospatial and graph rendering: + +1. **SVG First:** For small, highly interactive, and easily stylable graphs (typically < 1,000 nodes), we default to SVG. It integrates perfectly with React, CSS, and DOM events. +2. **Canvas 2D as Primary Large-Data Renderer:** For medium to large datasets (1,000 to ~10,000 nodes) where SVG DOM overhead becomes a bottleneck, we will use Canvas 2D. Canvas can efficiently render thousands of points and lines without freezing the browser UI. +3. **WebGL/Deck.gl as Last Resort:** We will only introduce WebGL (e.g., Deck.gl) if measured thresholds prove that Canvas 2D is insufficient for a specific use case (e.g., consistently rendering > 10,000 nodes with high-frequency updates or 3D requirements). + +## Consequences +- **Positive:** Keeps the application bundle size small. Development remains simpler using familiar React and Canvas APIs. Reduces the risk of over-engineering early in the project. +- **Negative:** We may need to rewrite a visualization from Canvas to WebGL later if a dataset grows beyond the 10,000 node threshold unexpectedly. +- **Mitigation:** We encapsulate rendering logic behind a generic `GeospatialRenderer` component interface to allow swapping underlying technologies (SVG vs Canvas) without changing the consumer API. diff --git a/docs/moon/CHANGELOG.md b/docs/moon/CHANGELOG.md new file mode 100644 index 0000000..75eeaab --- /dev/null +++ b/docs/moon/CHANGELOG.md @@ -0,0 +1,81 @@ +# Changelog + +All notable changes to this project are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Changed + +- Flattened the documentation dashboard from `docs/website/react/` into `docs/website/` (PMF-style package root), retargeted Docusaurus/TypeDoc/Storybook paths, and pointed Storybook at `src/frameworks/react/components/`. +- Moved Astro island routes from `src/pages/` into `src/frameworks/astro/pages/` and pointed `astro.config.mjs` `srcDir` at `./src/frameworks/astro` so Astro sources (pages, components, SFCs) live under one framework tree. + +### Added + +- Docs website parity modules under `docs/website/src/`: `configs/`, `constants/`, `enums/`, `graphql/`, `hooks/`, `interfaces/`, `simulations/`, `utils/`, `stories/` (research lore), `libraries/{form,motion,router,redux}`, `frameworks/{react,vue,aurelia,astro,shared}` (React `DocsWrapper`, Vue mount/directives, Aurelia island, ResearchOrbit Astro island, shared island utils). +- `docs/website/stack/{eslint,next}` with root `eslint.config.js` / `next.config.js` re-exports; `postcss.config.cjs` + Astro build into `static/astro-island/`; root package scripts `docs:*` and `docs:next:*`. +- Multi-framework platform roadmap ([`docs/moon/roadmaps/multi_framework_platform.md`](roadmaps/multi_framework_platform.md), MFP1–MFP16) covering React host + Vue/Astro/Aurelia islands, GraphQL schema/Apollo singleton, static-export fixtures, and WASM workers, grounded in architecture research and the current `src/frameworks` / `src/graphql` layout. +- Master roadmap R4 updates: workstream table entry, phase gate G6, timeline node R8, MFP implementation slices, risks X9–X11, and research anchors for multi-framework docs. +- Cross-links from simulations and infrastructure roadmaps into MFP for Apollo/WASM/island testing ownership. +- Architecture Decision Record (ADR 0002) for the graphics renderer lifecycle, deciding on isolated lazy islands over a persistent singleton canvas to respect strict bundle budgets (IF1). +- Capability-gated 3D hero model with intersection observer lazy-loading, strict resource disposal, webgl context loss recovery, and performance monitoring (IF2). +- Shared typed visualization primitives (scales, palettes, legends, tooltips) with accessible summaries and keyboard roving, integrating them into the `ResearchConstellation` component (IF3). +- Expanded interactive research constellation with nodes and edges linking core themes to specific projects (PCVRP, Audio) and publications, completing IF4. +- Reusable glTF/GLB model viewer (`ModelViewer.tsx`) with DRACO compression support, camera presets, collision-aware DOM annotations, and loading progress indicators (IF5). +- Equirectangular 360° panorama viewer (`PanoramaViewer.tsx`) with pointer drag, keyboard look, minimap, hotspot DOM overlays, and static image fallback (IF6). +- Audio-reactive signal-processing exhibit (`AudioExhibit.tsx`) with FFT visualization via Web Audio API, user-gesture requirement, and non-audio demo mode (IF7). +- Cinematic bounded effects (`Effects.tsx`) providing toggleable cursor spotlight, card tilt, particles, bloom/noise, and page distortion with reduced-motion preference awareness (IF8). +- Astro island architecture simulation via Web Components, mirroring the Aurelia integration pattern for multi-framework rendering. +- Architecture Decision Record (ADR 0003) and `GeospatialRenderer` component evaluating and implementing a progressive Canvas 2D / SVG hybrid rendering strategy for large graph/geospatial data (IF9). +- Research-observatory homepage foundation with semantic visual tokens, an accessible interactive research constellation, a capability-aware Three.js model, and a deterministic optimization-convergence simulation. +- Framework-neutral simulation layers under `src/simulations/` and a lazy Aurelia 2 island boundary under `src/aurelia/` so simulation engines can be shared across frontends. +- Unit and interaction tests for visualization utilities, constellation selection, deterministic simulation generation, lifecycle transitions, and simulation controls. +- A root ESLint configuration so `npm run lint` performs a non-interactive `next/core-web-vitals` check. +- Moved shared content types to `src/interfaces/types.ts`; implemented typed Redux actions, reducers, store hooks, provider, and browser-safe theme persistence under `src/redux/`. +- Reorganized interactive domains into `components/audio`, `books`, `canvas`, `games`, `graph`, `image`, `maps`, `models`, `routes`, and `video`; added fleet routing, ML spectrum, research shelf, game prototype, media mosaic, and storyboard elements. +- A research-driven, milestone-based product roadmap for progressively delivering an accessible visual system, interactive data stories, 3D/360 experiences, browser ML, and mathematical-optimization labs. +- Detailed acceptance criteria, dependency maps, device-quality tiers, performance budgets, fallbacks, accessibility requirements, and release gates across every feature roadmap. +- Dark/light theme toggle in the header, with the sidebar keeping the current theme across navigation. +- "AI" section under Tools; "Other" content section. +- PCVRP report and the audio signal processing report/post, with dedicated styling. +- Cypress e2e tests (`cypress/e2e/`) and Jest unit tests (`src/components/__tests__/`). +- License, research, and reports content; notebooks workspace switched from Conda to `uv`. +- Feature-themed roadmaps under `docs/moon/roadmaps/` (user interface, interactive features, mathematical optimization, machine learning, documentation). +- Renamed simulation boundaries to `src/simulations/repository` (contracts/types) and `src/simulations/scenarios` (presets), with imports and tests updated. +- Added the [Interactive Features and Visual Storytelling Research report](research/Interactive%20Features%20and%20Visual%20Storytelling%20Research.md), covering academic, standards, geospatial, corporate, and practitioner references for fleet optimization, ML, games, media, books, 3D/360, audio, and WebGPU. +- Added Image-Toolkit-style roadmap parity: timeline/phase gates, stable RR1–RR10 packages, acceptance evidence, risk registers, effort matrix, and document history across all moon roadmaps. +- `test/integration/`: RTL integration tests exercising `ClientLayoutWrapper` (Header + Sidebar + Footer composed together) plus an MSW-backed network-layer test (`test/integration/mocks/`). +- `test/cypress/smoke/`: fast Cypress smoke tests — every top-level route renders its layout shell, the homepage logs no console errors, and the theme toggle works. +- Root `benchmark/` performance harness with static-export route checks, payload totals, largest-asset reporting, configurable budgets, and reproducible JSON output. +- Comprehensive root README documentation for launching the documentation website, navigating the new source structure, running benchmarks, authoring content, and maintaining roadmap/issue state. +- Expanded `docs/ARCHITECTURE.md` with system/build/request Mermaid diagrams, module contracts, rendering tiers, simulation/Redux/worker boundaries, and TypeScript/React implementation excerpts. + +### Changed + +- Replaced Jest with Vitest for unit tests; moved `src/components/__tests__/` to `test/unit/components/`, mirroring `src/components/`'s `layout/`/`ui/` split. +- Moved `cypress/` to `test/cypress/` (config included); CI's Cypress step and the `npm run cypress:*` scripts run from that directory since Cypress resolves spec globs relative to the current working directory, not `--config-file`. +- `npm run build` now runs a `postbuild` step that symlinks `out/github-pages -> .`, so `npm start` (a plain static file server) answers under `/github-pages` locally the same way GitHub Pages does — needed for Cypress/Lighthouse CI jobs that serve the production build rather than `next dev`. +- Infrastructure roadmap now includes the R3 benchmark implementation slice and documents the initial baseline's existing media and aggregate-bundle budget exceptions. +- Fixed the master roadmap Mermaid timeline by replacing invalid chained class syntax with portable `class` assignments; benchmark output now includes a Markdown review summary alongside JSON. + +## [0.2.0] — migration to Next.js + +### Changed + +- Moved from Gatsby to Next.js as the site framework. +- Updated PostCSS/Tailwind configuration for the new framework. + +## [0.1.0] — Gatsby + TypeScript skeleton + +### Added + +- Replaced the previous Jekyll implementation with a Gatsby + React + TypeScript skeleton. +- Jupyter notebooks, layouts/pages, doc assets, and the markdown generator for publications/talks/references. + +## [0.0.1] — Jekyll (minima) + +### Added + +- Initial site built on Jekyll's `minima` theme: layouts, Sass styles, example posts, and GitHub Pages config. diff --git a/docs/moon/ROADMAP.md b/docs/moon/ROADMAP.md new file mode 100644 index 0000000..802d4ff --- /dev/null +++ b/docs/moon/ROADMAP.md @@ -0,0 +1,364 @@ +# github-pages — Immersive Research Portfolio Master Roadmap + +**Last updated:** 2026-08-09 · **Roadmap session:** R4 · **Delivery model:** static-first progressive enhancement + +This is the product-level index. Each workstream file is an issue-ready implementation document with options, acceptance criteria, tests, budgets, risks, and a history of scope changes. Primary interactive evidence: [`Interactive Features and Visual Storytelling Research.md`](research/Interactive%20Features%20and%20Visual%20Storytelling%20Research.md). Multi-framework / GraphQL / WASM evidence: [`Next.js Multi-Framework Architecture.md`](research/Next.js%20Multi-Framework%20Architecture.md), [`React Hosting Vue Micro-Frontends.md`](research/React%20Hosting%20Vue%20Micro-Frontends.md), plus architecture notes in [`Advanced Web Portfolio Architecture Research.md`](research/Advanced%20Web%20Portfolio%20Architecture%20Research.md) and [`Global Interactive Portfolio Website Architecture.md`](research/Global%20Interactive%20Portfolio%20Website%20Architecture.md). + +## Table of contents + +- [Vision and operating rules](#vision-and-operating-rules) +- [Implementation timeline](#implementation-timeline) +- [Current state](#current-state) +- [How to use this roadmap](#how-to-use-this-roadmap) +- [Phase gates](#phase-gates) +- [Workstreams](#workstreams) +- [Research-derived feature index](#research-derived-feature-index) +- [Dependency and risk register](#dependency-and-risk-register) +- [Effort × impact matrix](#effort--impact-matrix) +- [Anchor index](#anchor-index) +- [Document history](#document-history) + +## Vision and operating rules + +Turn the site into a beautiful, legible research observatory: visitors can understand waste-fleet routing, deep reinforcement learning, optimization trade-offs, game-development experiments, media references, and technical/political history through carefully staged interaction. + +1. **Explain before embellishing.** The claim, data, units, source, and next action are visible before a canvas or model loads. +2. **One fact, multiple senses.** Every visualization has DOM text, keyboard controls, a list/table or download, and a reduced-motion path. +3. **Static export is a product constraint.** No runtime API, token, secret, or required server is assumed. Backend proposals are explicit forks. +4. **Capability tiers are policy, not guesses.** Static, reduced, and full tiers respond to preferences, hardware, browser support, visibility, and measured performance. +5. **Research honesty is visual.** Show assumptions, uncertainty, feasibility, incumbent/bound/gap, confidence, limitations, and provenance. +6. **Local by default.** Local audio, notes, annotations, simulation inputs, and future inference inputs stay on-device unless a visitor explicitly opts in. +7. **Every effect has a budget.** LCP ≤ 2.5 s, INP ≤ 200 ms, CLS ≤ 0.1 at p75; initial route JS ≤ 200 kB gzip excluding framework runtime; optional island ≤ 300 kB gzip; initial 3D/media ≤ 2 MB. + +## Implementation timeline + +> **Legend:** node fill = work type (blue feature, violet augmentation, cyan infrastructure, amber performance, green docs, slate research); border = status (green complete, amber in progress, slate planned, red blocked). `==>` is a blocking dependency, `-->` sequential, `---` complementary. + +```mermaid +flowchart LR + classDef feature fill:#2563eb,color:#fff + classDef augment fill:#7c3aed,color:#fff + classDef infra fill:#0891b2,color:#fff + classDef perf fill:#ea580c,color:#fff + classDef docs fill:#15803d,color:#fff + classDef research fill:#475569,color:#fff + classDef done stroke:#16a34a,stroke-width:4px + classDef active stroke:#d97706,stroke-width:4px + classDef planned stroke:#64748b,stroke-width:2px + + R0["R0 Foundation\nstatic site + tests"]:::infra + R1["R1 Visual language\ntokens + motion"]:::augment + R2["R2 Explainable\nvisual primitives"]:::feature + R3["R3 Fleet observatory\nmap + solver replay"]:::feature + R4["R4 ML lab\nmodel + audio"]:::feature + R5["R5 Culture room\nmedia + books + game"]:::feature + R6["R6 Spatial tier\n360 + WebGL"]:::augment + R7["R7 WebGPU frontier\nworkers + optional XR"]:::research + R8["R8 Polyglot islands\nVue + GraphQL/Apollo + WASM"]:::feature + Q["Quality gates\nbudgets + a11y"]:::perf + D["Docs + provenance\nresearch reports"]:::docs + class R0 done + class R1,R2,Q,D active + class R3,R4,R5,R6,R7,R8 planned + R0 ==> R1 ==> R2 ==> R3 + R2 --> R4 + R2 --> R5 + R3 --> R6 + R4 --> R7 + R2 --> R8 + R0 ==> R8 + Q ==> R2 + Q ==> R3 + Q ==> R8 + D --- R2 + D --- R5 + D --- R8 +``` + +## Current state + +### Shipped or partially shipped (R1–R3) + +- Next.js 14 static export, content routes, theme shell, Vitest/RTL/MSW, Cypress, and notebooks workspace. +- Tokenized observatory homepage with Three.js model fallback, graph/DOM constellation, deterministic optimization-convergence simulation, and field-note components. +- Domain UI under `src/frameworks/react/components/{audio,books,canvas,games,graph,image,maps,models,routes,video,visualization,layout,ui}`; typed contracts under `src/interfaces`; Redux under `src/libraries/redux`. +- Framework-neutral simulations with `repository/` types, `scenarios/` presets, `generator/` computation, and `context/` lifecycle. +- **Multi-framework seeds:** React host; Astro island via `src/frameworks/astro` + `public/astro-island` iframe wrapper; Aurelia convergence island via `src/frameworks/aurelia` mount/unmount; `src/frameworks/shared` utilities. +- **GraphQL seed:** `src/graphql/schema.graphql` is still a placeholder (`Query._empty`); `fragments/` empty; no Apollo Client dependency yet. +- **WASM:** not in tree; research-backed paths only (solvers / edge ML). +- Interactive graphics IF1–IF13 marked complete in their workstream; root `benchmark/` harness records static-export budgets. +- Production static export succeeds; multi-framework test matrix not yet established. + +### Open risks + +- Route map and solver are currently illustrative SVG/Canvas/recorded traces; they do not claim live municipal data or optimality. +- Vue 3 is research-only; dual-runtime cost and Next App Router loader conflicts are unproven in this repo. +- GraphQL/Apollo must remain static-export safe—live HTTP GraphQL would violate the no-runtime-API product constraint unless explicitly forked. +- Astro today is iframe-based, not a first-class SFC compile path inside Next webpack. +- WASM OOM / main-thread jank if solvers or models load without workers, memory gates, and pure-TS fallbacks. +- Module Federation is a poor fit for App Router + static export; avoid defaulting to it (see multi-framework roadmap). + +## How to use this roadmap + +Each workstream section follows the Image-Toolkit convention: + +1. Read the timeline and current-status table before starting. +2. Read the item’s problem statement, options/trade-offs, recommendation, acceptance criteria, test plan, performance budget, and risks. +3. Link implementation to a stable ID and GitHub issue. Mark `Partial` when only the minimum slice shipped; do not silently convert a discovery item to done. +4. Record measured results, deviations, and residual risks in the item and changelog. Update the dependency graph when scope changes. + +Status vocabulary: ✅ Done · 🔄 Partial/in progress · ⬜ Planned · 🔬 Research · ⛔ Blocked. Effort: S (<2 days), M (2–7 days), L (1–3 weeks), XL (multi-week/architecture fork). + +## Phase gates + +### Gate G0 — Static contract (complete) + +Core routes build on GitHub Pages, semantic content survives disabled JavaScript, and unit/integration/e2e tests run deterministically. + +### Gate G1 — Visual system (in progress) + +Tokens, responsive hierarchy, reduced motion, focus visibility, contrast, and a capability policy exist before additional effects are added. Exit: Lighthouse baseline and manual keyboard/screen-reader smoke. + +### Gate G2 — Explainable interaction (in progress) + +Shared scales, palettes, legends, selection, URL state, summaries, tables, and fixture data exist. Exit: a visitor can reproduce the same conclusion from the visual and DOM representations. + +### Gate G3 — Computational storytelling (planned) + +Fleet route playback, solver comparison, model replay, and audio are worker/fixture-safe. Exit: cancellation, stale-result protection, metrics, provenance, and performance evidence. + +### Gate G4 — Spatial/media stories (planned) + +3D model/panorama assets have manifests, licenses, thumbnails, quality tiers, and disposal tests. Exit: flat/static fallback and mobile profile remain within budgets. + +### Gate G5 — GPU/experimental frontier (research) + +WebGPU, splats, WebXR, and large graphs are only promoted after a device matrix proves a user-facing benefit. Exit: WebGL/SVG fallback, privacy review, and a documented support matrix. + +### Gate G6 — Polyglot platform (planned) + +React host remains authoritative; Vue/Astro/Aurelia islands load client-only with fallbacks; GraphQL schema + Apollo singleton work from static fixtures; optional WASM worker has pure-TS fallback. Exit: MF-G1–MF-G3 in [multi_framework_platform.md](roadmaps/multi_framework_platform.md), per-island budgets, and offline tests. + +## Workstreams + +| Workstream | IDs | Detail | Current state | +| --- | --- | --- | --- | +| Visual design and UX | UI1–UI14 | [user_interface.md](roadmaps/user_interface.md) | 🔄 UI3–UI5/UI13–UI14 partial | +| Interactive graphics | IF1–IF13 | [interactive_features.md](roadmaps/interactive_features.md) | ✅ IF1–IF13 (catalogue + experiments shipped) | +| Simulations and Aurelia | SIM1–SIM10 | [simulations_and_aurelia.md](roadmaps/simulations_and_aurelia.md) | 🔄 SIM1–SIM4 partial | +| Multi-framework platform | MFP1–MFP16 | [multi_framework_platform.md](roadmaps/multi_framework_platform.md) | 🔄 React/Astro/Aurelia seeds; Vue/Apollo/WASM ⬜ | +| Mathematical optimization | MO1–MO8 | [mathematical_optimization.md](roadmaps/mathematical_optimization.md) | ✅ MO1; ⬜ MO2+ | +| Machine learning | ML1–ML8 | [machine_learning.md](roadmaps/machine_learning.md) | ✅ ML1; ⬜ ML2+ | +| Infrastructure and quality | IT1–IT14 | [infrastructure_and_testing.md](roadmaps/infrastructure_and_testing.md) | ✅ IT1–IT5; ⬜ IT6+ | +| Documentation/content | DOC1–DOC11 | [documentation.md](roadmaps/documentation.md) | 🔄 DOC3; ⬜ DOC4+ | +| Research-derived interaction | RR1–RR10 | [research report](research/Interactive%20Features%20and%20Visual%20Storytelling%20Research.md) | 🔬 research captured | + +### R3 implementation slices + +| Slice | Implementation approach | Evidence gate | +| --- | --- | --- | +| IT-B1 | Artifact-first benchmark with local server and deterministic routes | `benchmark/results/latest.json`, route status 200 | +| DOC-B1 | Root README launch/structure/runbook and architecture diagrams | README and architecture exceed maintenance-detail threshold | +| MO-B1 | Route playback remains static-first before solver worker integration | deterministic fixture and table equivalent | +| ML-B1 | Model-card contract precedes runtime/model download | CPU fixture, version, limitations, privacy note | +| IF-B1 | Domain islands remain independently importable | bundle and fallback checks | +| MFP-B1 | Island host contract + ADR; no federation-by-default | ADR + `src/frameworks` layout | +| MFP-B2 | GraphQL schema v1 + static fixtures (no live API) | schema + MSW/fixture tests | +| MFP-B3 | First Vue or hardened Aurelia parity demo | dual-framework a11y table | +| MFP-B4 | WASM worker stub with pure-TS fallback | cancel + seed parity tests | + +## Detailed implementation playbook + +This section turns the roadmap IDs into repeatable engineering decisions. It is intentionally explicit so that an issue can be implemented without rediscovering the architecture. + +### 1. Prepare the visitor question + +Every feature begins with a sentence of the form “A visitor should be able to understand or compare ___ by interacting with ___.” The first render contains that sentence, the units, the source/provenance link, and the next action. If the sentence cannot be stated without promising a scientific conclusion, the feature is a research prototype and must use illustrative language. + +### 2. Define the serializable contract + +Create or extend an interface under `src/interfaces` or `src/simulations/repository`. Prefer literal unions for status and quality tiers, readonly arrays for fixture data, explicit units in field names or documentation, and nullable fields for unavailable measurements. Avoid classes, functions, DOM nodes, browser handles, and implicit dates. Add a fixture that represents a valid, empty, invalid, and degraded state. + +### 3. Build the non-visual path + +Render semantic headings, paragraphs, lists, tables, links, labels, and buttons first. The fallback must answer the visitor question without CSS animation, WebGL, WebGPU, audio, a map tile, or a network request. Give it a stable test selector only when a semantic role or accessible name is insufficient. + +### 4. Add local interaction + +Use component state for hover, focus, drag, playback cursor, filters, and drafts. Use a controller for simulation lifecycle. Use Redux only for preferences or cross-route selections. Keyboard actions mirror pointer actions and have visible focus. URL state is added only when a visitor benefits from sharing or refreshing a view. + +### 5. Add the enhancement behind a capability boundary + +Load Three.js, audio analysis, map layers, or future GPU code after visibility/intent. Respect reduced motion and quality preference before allocating resources. Catch initialization and context-loss failures, return the fallback, and report a concise status message. Do not let a dynamic import change the document's layout. + +### 6. Teardown and test + +The owner of an animation frame cancels it. The owner of an event listener removes it. The owner of an audio context suspends/closes it. The owner of a worker terminates it. The owner of an object URL revokes it. Tests mount, interact, unmount, and verify no stale update occurs afterward. + +### 7. Measure and document + +Run the artifact benchmark, capture route bytes and largest assets, and record any intentional budget exception. For graphics, add a representative device profile and frame/heap observation. Update the relevant roadmap row, changelog, architecture note, and GitHub issue before calling the slice In review or Done. + +## Workstream implementation details + +### Visual UI (UI1–UI14) + +- Establish tokens for surface, text, border, focus, accent, spacing, radius, type scale, and motion. +- Keep light/dark themes semantic; components consume tokens rather than hard-coded colors. +- Reserve media and canvas dimensions to prevent layout shift. +- Test 320, 768, and 1440 px widths, forced colors, 200% zoom, keyboard-only navigation, and reduced motion. +- Use View Transitions only as an enhancement; browser navigation and focus restoration remain correct without it. + +### Interactive graphics (IF1–IF13) + +- Separate data encodings from renderers so SVG, Canvas, and WebGL can share scales and legends. +- Keep selection represented in the DOM and announce it without frame-by-frame live-region noise. +- Prefer event-driven SVG/canvas for small fixtures; evaluate deck.gl/WebGPU only after thresholds are measured. +- Keep model annotations in DOM content and expose a sequential annotation index. +- Store asset manifests with license, dimensions, compression, quality tier, and fallback poster. + +### Mathematical optimization (MO1–MO8) + +- Start with seeded fixtures for depot, vehicle capacity, demand, time windows, and dropped visits. +- Provide baseline and heuristic traces before adding a solver runtime. +- Label feasible, infeasible, timed out, incumbent, bound, gap, best-known, and proven-optimal states distinctly. +- Move long computation to a worker with request IDs, cancellation, progress, and typed-array transfer only after profiling. +- Export scenario and result JSON so a reader can reproduce a chart and inspect assumptions. + +### Machine learning (ML1–ML8) + +- Define a model card before selecting a runtime: task, data, preprocessing, version, provider, latency, memory, limitations, and license. +- Ship a deterministic recorded replay and CPU/static fallback before WebGPU or WASM acceleration. +- Keep local inputs local and state clearly when a model is illustrative rather than validated. +- Visualize reward/cost, confidence, policy choice, and error cases with a table equivalent. +- Test unsupported operators, corrupt model cache, cancellation, and out-of-memory messaging. + +### Simulations and Aurelia (SIM1–SIM10) + +- Keep `repository`, `scenarios`, `generator`, and `context` free of view imports. +- Require React and Aurelia to consume the same snapshot contract and deterministic seed. +- Mount Aurelia only in an island with a static fallback and a strict unmount path. +- Keep worker protocol versioned and reject stale responses. +- Document assumptions and validity limits beside every scenario fixture. +- Cross-link platform concerns (Apollo, Vue, WASM toolchain) to MFP\* rather than bloating SIM\*. + +### Multi-framework platform (MFP1–MFP16) + +- Treat React/Next as the only host for routing and GitHub Pages static export. +- Load Vue/Aurelia as client-only islands after intent/visibility; never in RSC/Node pre-render. +- Prefer colocated islands or Web Components over App Router Module Federation. +- Put GraphQL schema under `src/graphql`; Apollo core under `src/libraries/apollo` with **no** React imports; adapters per framework. +- Default GraphQL to build-time fixtures + MSW; live HTTP is an explicit backend fork. +- Put WASM loaders under `src/libraries/wasm` (or `src/wasm`); accelerate SIM/MO/ML only with pure-TS fallbacks and memory gates. +- Enforce per-island gzip budgets and mount/unmount leak tests before claiming polyglot architecture. + +### Infrastructure and testing (IT1–IT14) + +- Keep lint, typecheck, unit, integration, browser smoke, build, and benchmark commands independently runnable. +- Treat benchmark output as evidence, not a score; explain regressions in issues. +- Add a browser matrix for WebGL unavailable/context lost, reduced motion, slow network, and coarse pointer. +- Run dependency/license checks before adding media, models, runtimes, or map providers. +- Record ten-navigation heap/context probes for persistent graphics owners. + +### Documentation (DOC1–DOC11) + +- Give each feature an overview, data contract, lifecycle, accessibility equivalent, browser support, fallback, test plan, benchmark, and license section. +- Link a stable roadmap ID and GitHub issue from implementation notes. +- Distinguish academic evidence, standards guidance, corporate examples, personal inspiration, and measured repository results. +- Preserve historical rationale in changelog and document history tables. + +## Definition-of-done template + +Copy this template into an issue before implementation: + +```markdown +## Visitor question +## Roadmap ID and dependencies +## Data contract and provenance +## Static/reduced/full rendering paths +## Keyboard and screen-reader behavior +## Failure, cancellation, and teardown behavior +## Tests and deterministic fixtures +## Benchmark before/after +## Licensing and privacy review +## Changelog and documentation updates +``` + +## Review gates + +| Gate | Reviewer asks | Evidence | +| --- | --- | --- | +| Content | Can the claim be read without JavaScript? | static route and text fallback | +| Contract | Are units, status, provenance, and failure states explicit? | interface and fixtures | +| Interaction | Can keyboard and reduced-motion visitors reach the same conclusion? | tests and manual path | +| Lifecycle | Are resources released after hidden/unmount/error? | teardown test/profile | +| Performance | Did route bytes or first interaction regress? | benchmark result | +| Honesty | Does wording distinguish illustrative from measured/optimal? | copy review and model card | +| Operations | Is the issue/status/changelog/roadmap synchronized? | project item and commit | + +## Document history + +| Date | Revision | Change | +| --- | --- | --- | +| 2026-08-09 | R4 | Added multi-framework platform workstream (MFP1–MFP16), gate G6, R8 timeline node, and research links for Vue/Astro/Aurelia/GraphQL/Apollo/WASM. | +| 2026-08-08 | R3 | Added implementation playbook, workstream approaches, definition-of-done template, and review gates. | + +## Research-derived feature index + +| ID | Feature | Primary workstream | Evidence and next slice | +| --- | --- | --- | --- | +| RR1 | Cited research/source graph and reading room | DOC/UI | Narrative visualization + accessible graph; static first | +| RR2 | Waste-fleet route playback | MO/IF | deck.gl TripsLayer/Mapbox patterns + OR-Tools semantics | +| RR3 | Solver/heuristic/Pareto comparison | MO/IF | incumbent, bound, gap, feasibility and export | +| RR4 | ML training/policy replay and model card | ML/IF | interactive ML + Manifold model comparison | +| RR5 | Local/demo audio spectrum and spectrogram | ML/IF | MDN `AnalyserNode`, explicit gesture/teardown | +| RR6 | Media/reading timeline and argument graph | UI/DOC | narrative chapters, citations, uncertainty | +| RR7 | Playable game mechanic and devlog | UI/IF | small island, pause/restart, storyboard fallback | +| RR8 | Annotated 360° media room | IF | Three.js panorama, sequential hotspot alternative | +| RR9 | WebGPU route/graph aggregation experiment | IF/IT | capability gate, WebGL/SVG fallback | +| RR10 | Shared worker protocol and replay export | SIM/IT | versioned messages, cancellation, transferables | + +## Dependency and risk register + +| ID | Dependency/risk | Detection | Mitigation / decision | +| --- | --- | --- | --- | +| X1 | Shared visualization semantics drift | same data encoded differently | typed scales/palettes/legend contract under IF3 | +| X2 | Map/vendor token or tile outage | fixture route fails to render | SVG/Canvas fixture + adapter; no client secret | +| X3 | Solver overclaim | no proof/bound/timeout shown | feasibility/incumbent/bound/gap/status fields required | +| X4 | WebGL context/VRAM growth | ten-navigation heap/context probe | one owner, disposal, visibility suspension, reduced tier | +| X5 | Main-thread jank | long-task/frame profile | worker, event-driven SVG, progressive chunks | +| X6 | Accessibility gap | hover-only or chart-only insight | DOM summary/table, keyboard tree/list, user testing | +| X7 | ML privacy/energy | large download or upload | tiny opt-in model, local-only inputs, static trace | +| X8 | Scope inflation | effect added without claim | item must state visitor question and exit metric | +| X9 | Dual-framework runtime cost | INP / long tasks with Vue+React | one secondary framework per route; intent hydration; MFP15 budgets | +| X10 | GraphQL runtime dependency | network in static export | fixtures + MSW only by default (MFP11) | +| X11 | WASM memory / unsupported browsers | instantiate fail | pure-TS fallback + capability gate (MFP12–MFP13) | + +## Effort × impact matrix + +| | High impact | Medium impact | Discovery | +| --- | --- | --- | --- | +| S | RR1 source cards; IT6 budget baseline | UI13 taxonomy cleanup | MFP1 ADR + layout | +| M | RR5 audio; RR6 timeline; SIM5 runner; MFP8–MFP9 schema/client | UI8 search; DOC6 embed guide; MFP5 Astro build | RR9 WebGPU spike | +| L | RR2 fleet playback; RR3 comparison; RR4 ML replay; MFP4 Vue island; MFP10–MFP11 | RR7 game island; RR8 panorama; MFP13 WASM worker | RR10 worker protocol hardening | +| XL | — | — | WebGPU splats/XR; live backend solver; MFP14 edge-ML WASM | + +## Anchor index + +- [RR research report](research/Interactive%20Features%20and%20Visual%20Storytelling%20Research.md) +- [Multi-framework architecture research](research/Next.js%20Multi-Framework%20Architecture.md) +- [Vue-in-React host research](research/React%20Hosting%20Vue%20Micro-Frontends.md) +- [UI visual system](roadmaps/user_interface.md) +- [IF graphics](roadmaps/interactive_features.md) +- [SIM simulations/Aurelia](roadmaps/simulations_and_aurelia.md) +- [MFP multi-framework platform](roadmaps/multi_framework_platform.md) +- [MO optimization](roadmaps/mathematical_optimization.md) +- [ML browser ML](roadmaps/machine_learning.md) +- [IT quality](roadmaps/infrastructure_and_testing.md) +- [DOC documentation](roadmaps/documentation.md) + +## Document history + +- 2026-08-09 — R4: multi-framework platform (React/Vue/Astro/Aurelia + GraphQL/Apollo + WASM) workstream and gate G6. +- 2026-08-08 — R2: added research-derived RR1–RR10 index, phase gates, risk register, Image-Toolkit-style timeline/status conventions, and explicit current-state accounting. +- 2026-08-08 — R1: established the immersive portfolio vision and initial feature workstreams. diff --git a/docs/moon/component_catalogue.md b/docs/moon/component_catalogue.md new file mode 100644 index 0000000..eaed351 --- /dev/null +++ b/docs/moon/component_catalogue.md @@ -0,0 +1,135 @@ +# Interactive Component Catalogue + +This document serves as the registry for all interactive components across the repository. Each domain-specific component is documented below according to the IF13 standard: its input contract, data source, accessibility equivalent, loading behavior, and resource ownership. + +## 1. Audio Domain + +### `AudioExhibit` (`src/frameworks/react/components/audio/AudioExhibit.tsx`) +* **Input Contract**: Takes no props (currently standalone demo). +* **Data Source**: Local media file or synthetic oscillator (demo mode). +* **Accessibility Equivalent**: Textual state readout ("Playing", "Paused") and visually-hidden status alerts. +* **Loading Behavior**: Lazy-loaded `AudioContext` only initialized upon explicit user gesture (Play button click). +* **Resource Ownership**: Owns a Web Audio `AudioContext`, an `AnalyserNode`, and an HTML `` for FFT rendering. + +### `AudioSpectrum` (`src/frameworks/react/components/audio/AudioSpectrum.tsx`) +* **Input Contract**: None (demo display). +* **Data Source**: Procedurally generated visual frequency data. +* **Accessibility Equivalent**: Described as a decorative ambient element via `aria-hidden` or `aria-label`. +* **Loading Behavior**: Rendered immediately on the client. +* **Resource Ownership**: Does not own hardware resources (pure CSS/DOM animation). + +--- + +## 2. Books Domain + +### `ResearchShelf` (`src/frameworks/react/components/books/ResearchShelf.tsx`) +* **Input Contract**: Optional array of book objects (title, author, cover image). +* **Data Source**: Static JSON/Mock data. +* **Accessibility Equivalent**: Semantic `
    ` list of books with standard `...` tags. +* **Loading Behavior**: Immediately rendered, images lazily decoded by browser. +* **Resource Ownership**: No heavy resources. + +--- + +## 3. Canvas Domain + +### `Effects` (`src/frameworks/react/components/canvas/Effects.tsx`) +* **Input Contract**: Preference flags for `reducedMotion`, `bloom`, `noise`, etc. +* **Data Source**: None (algorithmic). +* **Accessibility Equivalent**: Completely bypassable via `prefers-reduced-motion` media queries. +* **Loading Behavior**: Dynamically checks user preferences before rendering the Canvas. +* **Resource Ownership**: Owns an HTML `` and a `requestAnimationFrame` loop. + +### `FleetRouteCanvas` (`src/frameworks/react/components/canvas/FleetRouteCanvas.tsx`) +* **Input Contract**: `routeData` array of vehicle paths. +* **Data Source**: Computed optimization outputs from the backend/mock data. +* **Accessibility Equivalent**: Semantic data table of route coordinates and vehicle assignments. +* **Loading Behavior**: Loaded via Intersection Observer when scrolled into view. +* **Resource Ownership**: Owns a 2D `` context for high-performance rendering. + +--- + +## 4. Graph Domain + +### `ResearchConstellation` (`src/frameworks/react/components/graph/ResearchConstellation.tsx`) +* **Input Contract**: `nodes` and `links` arrays. +* **Data Source**: `src/constants/researchGraph.ts`. +* **Accessibility Equivalent**: Keyboard-roving list of nodes with ARIA live announcements for selection. +* **Loading Behavior**: Immediately rendered SVG elements. +* **Resource Ownership**: Owns SVG nodes, no heavy WebGL/Canvas context. + +--- + +## 5. Maps Domain + +### `GeospatialRenderer` (`src/frameworks/react/components/maps/GeospatialRenderer.tsx`) +* **Input Contract**: `nodes` (features), `links` (edges), and a threshold limit. +* **Data Source**: Geographic datasets (e.g., PCVRP instances). +* **Accessibility Equivalent**: Data table summary of locations and distances. +* **Loading Behavior**: Progressively enhanced based on node count (see ADR 0003). +* **Resource Ownership**: Owns a `` for datasets > 500 points, or purely SVG for smaller datasets. + +--- + +## 6. Models Domain + +### `HeroModel` (`src/frameworks/react/components/models/HeroModel.tsx`) +* **Input Contract**: None (hardcoded geometry). +* **Data Source**: Procedurally generated Three.js primitives. +* **Accessibility Equivalent**: Fallback static image and descriptive alt text. +* **Loading Behavior**: Isolated lazy island—Three.js imported only when visible. +* **Resource Ownership**: Owns a WebGL context (`WebGLRenderer`) and active requestAnimationFrame loop. + +### `ModelViewer` (`src/frameworks/react/components/models/ModelViewer.tsx`) +* **Input Contract**: `modelUrl` (glTF/GLB path). +* **Data Source**: External `.glb` files. +* **Accessibility Equivalent**: Annotations are rendered as standard DOM elements overlaying the canvas. +* **Loading Behavior**: Lazy-loads the `GLTFLoader` and model file. +* **Resource Ownership**: Owns a WebGL context, geometry buffers, and textures. + +### `PanoramaViewer` (`src/frameworks/react/components/models/PanoramaViewer.tsx`) +* **Input Contract**: `textureUrl` for equirectangular image. +* **Data Source**: External `.jpg` or `.png` panorama textures. +* **Accessibility Equivalent**: Flat image fallback with standard scroll navigation. +* **Loading Behavior**: IntersectionObserver triggered WebGL initialization. +* **Resource Ownership**: Owns a WebGL context and large texture buffers. + +--- + +## 7. Routes / Simulations Domain + +### `ConvergenceSimulation` (`src/frameworks/react/components/routes/ConvergenceSimulation.tsx`) +* **Input Contract**: Simulation ID string. +* **Data Source**: Simulation engine (`src/simulations/`). +* **Accessibility Equivalent**: ARIA live regions announcing current iteration, incumbent, and lower bound. +* **Loading Behavior**: React state-driven, initialized synchronously. +* **Resource Ownership**: No hardware resources, pure React/SVG. + +--- + +## 8. Video / Media Domain + +### `MediaReel` (`src/frameworks/react/components/video/MediaReel.tsx`) +* **Input Contract**: Array of video source URLs. +* **Data Source**: Local `/public` videos or remote URLs. +* **Accessibility Equivalent**: Native `