diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6cd676d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,17 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: npm install + - run: npm run build diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7a0b062 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,39 @@ +# CLAUDE.md + +Guidance for AI coding agents working in this repo. For human onboarding see +[`CONTRIBUTING.md`](CONTRIBUTING.md), which this file does not repeat. + +## What this repo is + +The **public website** for the Thoughtful AI Tools Lab (https://thoughtful-ai.com), +built with Astro 5 + React + Tailwind, deployed on Cloudflare. The lab's actual +research application is a *different* repo (`AIToolsLab/writing-tools`) — do not +look for app/model code here. + +## Build & verify + +- `npm install`, then `npm run build` must pass. CI (`.github/workflows/ci.yml`) + runs the build on every PR. Always run `npm run build` before claiming a change + works — there is no test suite, so a clean build is the bar. + +## Agent-specific notes + +- **Content is data-driven; prefer editing data over markup.** People are + per-person JSON files in `src/data/people/`; publications are an array in + `src/pages/publications.astro`. When adding similar content, follow the + existing data pattern instead of hand-writing new markup blocks. +- The page shell, SEO tags, and canonical URL live in `src/layouts/Layout.astro`. + The canonical URL is derived from the request path — don't hardcode it. +- The header is a React island (it has a mobile-nav toggle) hydrated with + `client:load` so it appears in static HTML — don't switch it back to + `client:only`. The footer is a static `.astro` component (no interactivity, no + hydration); keep static UI static rather than making it a React island. +- Match surrounding Tailwind utility classes; avoid introducing bespoke CSS. + +## Why this file is separate from CONTRIBUTING.md / README + +`README` orients a visitor ("what is this"), `CONTRIBUTING.md` onboards a human +contributor ("how to set up and where things live"), and `CLAUDE.md` is read +automatically by coding agents as standing instructions — so it focuses on the +constraints and verification steps an agent needs to not break things, and stays +short to keep the agent's context lean. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4878dd8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,47 @@ +# Contributing + +This repository is the **public website** for the Thoughtful AI Tools Lab +(https://thoughtful-ai.com). The research application itself lives in a separate +repo: [`AIToolsLab/writing-tools`](https://github.com/AIToolsLab/writing-tools). +Changes here are about how the lab presents its people, projects, vision, and +publications — not the writing tool's behavior. + +## Getting set up + +- Requires Node.js 18+ and npm. +- `npm install` — install dependencies. +- `npm run dev` — local dev server at http://localhost:4321. +- `npm run build` — production build into `dist/`. **CI runs this on every PR; if + it fails, the PR can't merge, so run it locally before pushing.** + +## Where things live + +| You want to change... | Edit... | +|-----------------------|---------| +| A team member (add yourself, mark graduated) | a JSON file in `src/data/people/` — see that folder's `README.md` | +| Publications | the `publications` array in `src/pages/publications.astro` | +| Projects | `src/pages/projects.astro` and the per-project pages in `src/pages/projects/` | +| The lab's vision statement | `src/pages/vision.md` | +| Site-wide header/footer/nav | `src/components/lab/` | +| Page shell, ``, SEO tags | `src/layouts/Layout.astro` | + +## Adding yourself to the team + +See [`src/data/people/README.md`](src/data/people/README.md). Short version: add +your photo to `public/people/`, copy `_example.json` to `your_name.json`, fill it +in, and open a PR. One file per person means no merge conflicts when several +people add themselves at once. + +## Conventions + +- Styling is [Tailwind CSS](https://tailwindcss.com/) utility classes. Match the + classes already used on nearby elements rather than introducing new CSS. +- Pages are [Astro](https://docs.astro.build/) (`.astro`); interactive UI is React + (`.tsx`) under `src/components/`. +- Keep content data-driven where a pattern exists (e.g. the people JSON files) so + future contributors edit data, not markup. + +## Deployment + +The site is deployed on **Cloudflare** (see `wrangler.jsonc`), serving the built +`dist/` directory. You don't need to deploy manually — merging to `main` is enough. diff --git a/README-astro.md b/README-astro.md index 7959c63..55fb640 100644 --- a/README-astro.md +++ b/README-astro.md @@ -45,9 +45,8 @@ Astro-based version of the Thoughtful AI Tools Lab website. ## Components -- `LabHeader`: Navigation header -- `LabHero`: Homepage hero -- `LabFooter`: Footer +- `Header`: Navigation header (React island) +- `Footer`: Footer (static Astro component) ## Content Management @@ -67,5 +66,5 @@ Astro-based version of the Thoughtful AI Tools Lab website. ## Deployment -Deploy to GitHub Pages or any static host. Output: `dist/`. +Deployed on Cloudflare (see `wrangler.jsonc`), serving the built `dist/` directory. diff --git a/astro.config.mjs b/astro.config.mjs index 9891045..41ca651 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -4,12 +4,11 @@ import react from "@astrojs/react"; // https://astro.build/config import tailwind from "@astrojs/tailwind"; - -const isProd = process.env.NODE_ENV === "production"; +import sitemap from "@astrojs/sitemap"; // https://astro.build/config export default defineConfig({ - integrations: [react(), tailwind()], + integrations: [react(), tailwind(), sitemap()], site: "https://thoughtful-ai.com", base: "/", }); diff --git a/package.json b/package.json index 2673b3a..3f94aed 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "license": "MIT", "dependencies": { "@astrojs/react": "^4.2.2", + "@astrojs/sitemap": "^3.7.3", "@astrojs/tailwind": "^6.0.2", "@heroicons/react": "^2.0.16", "@tailwindcss/typography": "^0.5.16", diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..1ab5ae5 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,4 @@ + + + T + diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..232ac5b --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://thoughtful-ai.com/sitemap-index.xml diff --git a/src/components/lab/Footer.astro b/src/components/lab/Footer.astro new file mode 100644 index 0000000..b1660eb --- /dev/null +++ b/src/components/lab/Footer.astro @@ -0,0 +1,25 @@ +--- +const year = new Date().getFullYear(); +--- + diff --git a/src/components/lab/LabHeader.tsx b/src/components/lab/Header.tsx similarity index 98% rename from src/components/lab/LabHeader.tsx rename to src/components/lab/Header.tsx index 264f935..9d9653c 100644 --- a/src/components/lab/LabHeader.tsx +++ b/src/components/lab/Header.tsx @@ -25,7 +25,7 @@ function NavList() { } -export default function LabHeader() { +export default function Header() { const [openNav, setOpenNav] = React.useState(false); React.useEffect(() => { diff --git a/src/components/lab/LabFooter.tsx b/src/components/lab/LabFooter.tsx deleted file mode 100644 index c5fa554..0000000 --- a/src/components/lab/LabFooter.tsx +++ /dev/null @@ -1,38 +0,0 @@ -export default function LabFooter() { - return ( - - ); -} diff --git a/src/data/people/README.md b/src/data/people/README.md new file mode 100644 index 0000000..0e28c9f --- /dev/null +++ b/src/data/people/README.md @@ -0,0 +1,34 @@ +# Team members + +Each person on the team is one JSON file in this folder. The About page +(`src/pages/about.astro`) reads every `*.json` file here automatically and sorts +people into **Current Team** and **Alumni**, so you never edit the page itself — +you just add or change a data file. + +## Add yourself (current students) + +1. Add your photo to `public/people/` (e.g. `public/people/your_name.png`). + A square image around 400×400px works well. If you don't add a photo, the + site falls back to an avatar generated from your initials. +2. Copy `_example.json` to a new file named after you, e.g. `your_name.json`. + (Files starting with `_` are ignored, so `_example.json` never appears on the site.) +3. Fill in the fields (see below), then open a pull request. + +Because everyone gets their own file, two students adding themselves at the same +time won't create a merge conflict. + +## Fields + +| Field | Required | Notes | +|----------|----------|-------| +| `name` | yes | Your full name. | +| `status` | yes | `"current"` while you're on the team, `"alumni"` after you leave. | +| `photo` | no | Path under `public/`, e.g. `/people/your_name.png`. Omit to use a generated avatar. | +| `role` | no | Defaults to "Undergraduate Researcher". | +| `order` | no | Lower numbers sort first in the Current Team list. Alumni are sorted alphabetically. | +| `link` | no | A personal site or GitHub profile; makes your name a link. | + +## When you graduate + +Change your own `status` from `"current"` to `"alumni"`. That's the only edit +needed — the About page moves you to the Alumni section automatically. diff --git a/src/data/people/_example.json b/src/data/people/_example.json new file mode 100644 index 0000000..ec8e7e5 --- /dev/null +++ b/src/data/people/_example.json @@ -0,0 +1,8 @@ +{ + "name": "Your Name", + "photo": "/people/your_name.png", + "role": "Undergraduate Researcher", + "status": "current", + "order": 2, + "link": "https://your-website.example" +} diff --git a/src/data/people/alina_sainju.json b/src/data/people/alina_sainju.json new file mode 100644 index 0000000..f3f11d9 --- /dev/null +++ b/src/data/people/alina_sainju.json @@ -0,0 +1,6 @@ +{ + "name": "Alina Sainju", + "photo": "/people/alina_sainju.jpg", + "role": "Undergraduate Researcher", + "status": "alumni" +} diff --git a/src/data/people/daniel_kim.json b/src/data/people/daniel_kim.json new file mode 100644 index 0000000..121c4dd --- /dev/null +++ b/src/data/people/daniel_kim.json @@ -0,0 +1,6 @@ +{ + "name": "Daniel Kim", + "photo": "/people/daniel_kim.png", + "role": "Undergraduate Researcher", + "status": "alumni" +} diff --git a/src/data/people/daniel_kwon.json b/src/data/people/daniel_kwon.json new file mode 100644 index 0000000..3f6a628 --- /dev/null +++ b/src/data/people/daniel_kwon.json @@ -0,0 +1,6 @@ +{ + "name": "Daniel Kwon", + "photo": "/people/daniel_kwon.png", + "role": "Undergraduate Researcher", + "status": "alumni" +} diff --git a/src/data/people/hannah_yoo.json b/src/data/people/hannah_yoo.json new file mode 100644 index 0000000..b90e16e --- /dev/null +++ b/src/data/people/hannah_yoo.json @@ -0,0 +1,6 @@ +{ + "name": "Hannah Yoo", + "photo": "/people/hannah_yoo.png", + "role": "Undergraduate Researcher", + "status": "alumni" +} diff --git a/src/data/people/jason_chew.json b/src/data/people/jason_chew.json new file mode 100644 index 0000000..cb2a7b1 --- /dev/null +++ b/src/data/people/jason_chew.json @@ -0,0 +1,6 @@ +{ + "name": "Jason Chew", + "photo": "/people/jason_chew.png", + "role": "Undergraduate Researcher", + "status": "alumni" +} diff --git a/src/data/people/jiho_kim.json b/src/data/people/jiho_kim.json new file mode 100644 index 0000000..1902c52 --- /dev/null +++ b/src/data/people/jiho_kim.json @@ -0,0 +1,7 @@ +{ + "name": "Jiho Kim", + "photo": "/people/jiho_kim.png", + "role": "Research Collaborator", + "status": "current", + "order": 1 +} diff --git a/src/data/people/kyle_houston.json b/src/data/people/kyle_houston.json new file mode 100644 index 0000000..d9cdc7f --- /dev/null +++ b/src/data/people/kyle_houston.json @@ -0,0 +1,5 @@ +{ + "name": "Kyle Houston", + "role": "Undergraduate Researcher", + "status": "alumni" +} diff --git a/src/data/people/monica_zhang.json b/src/data/people/monica_zhang.json new file mode 100644 index 0000000..daf0b3b --- /dev/null +++ b/src/data/people/monica_zhang.json @@ -0,0 +1,5 @@ +{ + "name": "Monica Zhang", + "role": "Undergraduate Researcher", + "status": "alumni" +} diff --git a/src/data/people/ray_flanagan.json b/src/data/people/ray_flanagan.json new file mode 100644 index 0000000..3c694e1 --- /dev/null +++ b/src/data/people/ray_flanagan.json @@ -0,0 +1,6 @@ +{ + "name": "Ray Flanagan", + "photo": "/people/ray_flanagan.png", + "role": "Undergraduate Researcher", + "status": "alumni" +} diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index 10bdfa1..7d74f87 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -1,6 +1,6 @@ --- -import LabHeader from "../components/lab/LabHeader"; -import LabFooter from "../components/lab/LabFooter"; +import Header from "../components/lab/Header"; +import Footer from "../components/lab/Footer.astro"; export interface Props { title?: string; metaDescription?: string; @@ -18,6 +18,8 @@ const frontmatter = Astro.props.frontmatter || {}; const title = Astro.props.title || frontmatter.title; const metaDescription = Astro.props.metaDescription || frontmatter.description || frontmatter.metaDescription; const prose = typeof Astro.props.prose !== 'undefined' ? Astro.props.prose : frontmatter.prose; + +const canonicalURL = new URL(Astro.url.pathname, Astro.site).href; --- @@ -25,7 +27,7 @@ const prose = typeof Astro.props.prose !== 'undefined' ? Astro.props.prose : fro - + {title} - Thoughtful AI Tools Lab - - + - +
{prose ? (
@@ -45,7 +46,7 @@ const prose = typeof Astro.props.prose !== 'undefined' ? Astro.props.prose : fro ) : ( )} - +