diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index dd84ea7..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index bbcbbe7..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a0f2dfd --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,54 @@ +name: build + +on: + pull_request: + push: + branches: [master] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + name: svelte-check + tests + build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: svelte-check + run: npm run check + + - name: Tests + run: npm test + + - name: Build + run: npm run build + + audit: + name: npm audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Audit + run: npm audit --audit-level=high diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 931af01..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: CI - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - lint: - name: Lint & Format - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '18' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: ESLint - run: npx eslint src/ - - - name: Prettier check - run: npx prettier --check "src/**/*.{astro,ts,tsx,js,jsx,css}" - - - name: Source tests (design, colors, theme, components) - run: npx vitest run tests/design-guidelines.test.js tests/color-contrast.test.js tests/theme-system.test.js tests/components.test.js - - build-and-test: - name: Build & Test - needs: lint - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '18' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: TypeScript check - run: npx astro check - - - name: Build - run: npx astro build - - - name: Accessibility & SEO tests - run: npx vitest run tests/accessibility-seo.test.js tests/seo-schemas.test.js - - - name: Security tests - run: npx vitest run tests/security.test.js - - - name: Performance & build output tests - run: npx vitest run tests/performance.test.js - - - name: Content quality & link validation - run: npx vitest run tests/content-quality.test.js - - - name: Infrastructure & resilience tests - run: npx vitest run tests/infrastructure.test.js diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..c8ad60a --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,22 @@ +name: lint + +on: + pull_request: + push: + branches: [master] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint: + uses: nyuchi/.github/.github/workflows/reusable-lint.yml@main + with: + # .svelte is excluded — formatting it needs prettier-plugin-svelte, + # which the org's reusable-lint workflow does not install. Svelte + # files stay locally-formattable; CI lints the rest. + prettier-glob: "**/*.{md,mdx,json,jsonc,ts,css,mjs}" diff --git a/.gitignore b/.gitignore index d76ce02..bfaaf3c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,24 @@ -# build output -dist/ -.astro/ +# SvelteKit +.svelte-kit +build -# dependencies -node_modules/ +# Vercel adapter output +.vercel -# logs -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* +# Dependencies +node_modules -# environment variables +# Environment .env -.env.production +.env.* +!.env.example -# macOS-specific files -.DS_Store +# Logs +npm-debug.log* +*.log -# IDE +# OS / IDE +.DS_Store .vscode/ .idea/ *.swp diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc new file mode 100644 index 0000000..6f188b0 --- /dev/null +++ b/.markdownlint-cli2.jsonc @@ -0,0 +1,13 @@ +// markdownlint-cli2 config — paths in scope for this repo. +{ + "config": { + "extends": ".markdownlint.jsonc", + }, + "globs": ["**/*.md", "**/*.mdx"], + "ignores": [ + "node_modules/**", + "**/.svelte-kit/**", + "**/.vercel/**", + "build/**", + ], +} diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc new file mode 100644 index 0000000..d411079 --- /dev/null +++ b/.markdownlint.jsonc @@ -0,0 +1,13 @@ +// markdownlint-cli2 config — relaxed defaults that match nyuchi/.github. +// +// See https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md +{ + "default": true, + "MD013": false, + "MD033": false, + "MD041": false, + "MD040": false, + "MD024": { "siblings_only": true }, + "MD007": { "indent": 2 }, + "MD060": false, +} diff --git a/.prettierignore b/.prettierignore index d85143e..6531a77 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,5 @@ -dist/ -node_modules/ -.astro/ +.svelte-kit +build +.vercel +node_modules package-lock.json -*.md diff --git a/.prettierrc b/.prettierrc index 0598874..de42adf 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,16 +1,7 @@ { "semi": true, - "singleQuote": true, + "singleQuote": false, "tabWidth": 2, "trailingComma": "all", - "printWidth": 100, - "plugins": ["prettier-plugin-astro"], - "overrides": [ - { - "files": "*.astro", - "options": { - "parser": "astro" - } - } - ] + "printWidth": 80 } diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..744516b --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,36 @@ +# yamllint config — matches nyuchi/.github. +# +# See https://yamllint.readthedocs.io/en/stable/configuration.html + +extends: default + +ignore: | + node_modules/ + .svelte-kit/ + .vercel/ + build/ + +rules: + line-length: + max: 140 + level: error + allow-non-breakable-words: true + allow-non-breakable-inline-mappings: true + + truthy: + check-keys: false + level: error + + document-start: disable + + comments: + min-spaces-from-content: 1 + + comments-indentation: disable + + indentation: + spaces: 2 + indent-sequences: consistent + + empty-lines: + max-end: 1 diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md deleted file mode 100644 index aa0c917..0000000 --- a/ARCHITECTURE.md +++ /dev/null @@ -1,373 +0,0 @@ -# System Architecture - -**Version**: 1.0 -**Last Updated**: February 2026 -**Status**: Authoritative. All development must follow this architecture. - ---- - -## Overview - -nyuchi learning follows a **layered architecture** designed for scalability, brand consistency, and maintainability. Every layer has a single responsibility. Changes flow downward through layers, never upward. This ensures that a styling change in one place propagates to every page, and content changes never require touching the shell or design system. - -``` - Layer 1: Site Shell (BaseLayout, header, footer, navigation, error handling) - Layer 2: Components (reusable UI primitives, Astro + React) - Layer 3: Styling (global.css design tokens, semantic classes, theme system) - Layer 4: Content (pages, blog posts, Sanity CMS data) -``` - -**The golden rule**: Content uses components. Components use styling. Styling uses tokens. Nothing reaches across layers. - ---- - -## Layer 1: Site Shell - -The site shell is the outermost wrapper that every page shares. It handles the concerns that are global to the entire site and should never need to be duplicated or customized per-page. - -### What the shell provides - -| Concern | Implementation | File | -|---------|----------------|------| -| **Document structure** | ``, `
`, ``, lang attribute | `BaseLayout.astro` | -| **SEO & metadata** | Meta tags, Open Graph, JSON-LD, canonical URLs | `SEO.astro` | -| **Navigation** | Header (desktop + mobile), dropdowns, hamburger menu | `BaseLayout.astro` | -| **Footer** | Link groups, wordmark, mission messaging, copyright | `BaseLayout.astro` | -| **Skip links** | 3 targets: main content, navigation, footer | `BaseLayout.astro` | -| **Theme system** | Light/dark/system toggle, localStorage persistence | `ThemeToggle.astro` | -| **Minerals Strip** | 4px brand strip, left edge, hidden on mobile | `BaseLayout.astro` | -| **Analytics** | Google Analytics script | `BaseLayout.astro` | -| **Animation system** | Intersection Observer, scroll-triggered animations | `BaseLayout.astro` | -| **Error boundaries** | 404 page, error states, graceful degradation | `pages/404.astro` (planned) | -| **Observability** | Analytics, performance monitoring, error tracking | Shell-level scripts | - -### Shell rules - -1. **Every page must use `BaseLayout`**. No page renders its own `` or ``. -2. **The shell is the single source of truth** for navigation, footer, and global scripts. -3. **Shell changes affect every page simultaneously**. This is intentional and desirable. -4. **Pages pass data to the shell via props** (`title`, `description`, `keywords`, `image`, `type`), never by modifying shell internals. - -### Error handling (planned) - -| Type | Implementation | Status | -|------|----------------|--------| -| **404 Not Found** | `src/pages/404.astro` using BaseLayout | Planned | -| **Error boundary** | Client-side React error boundary for interactive components | Planned | -| **Offline fallback** | Service worker with cached shell | Planned | -| **API error states** | Graceful degradation when Sanity CMS is unreachable | Planned | -| **Circuit breakers** | Timeout + fallback for external API calls (Sanity, analytics) | Planned | -| **Loading states** | Skeleton screens for async content | Planned | - ---- - -## Layer 2: Components - -Components are reusable UI building blocks. They accept props, render markup, and reference the styling layer for their appearance. Components never contain page-specific content or business logic. - -### Component types - -| Type | Technology | Location | Purpose | -|------|------------|----------|---------| -| **Astro components** | `.astro` | `src/components/` | Static, server-rendered UI | -| **React components** | `.tsx` | `src/components/ui/` | Interactive UI (Radix primitives) | - -### Current component inventory - -**Astro components** (site-specific): - -| Component | Purpose | -|-----------|---------| -| `Logo.astro` | Brand logo (3 variants, 3 sizes) | -| `SEO.astro` | Meta tags, Open Graph, JSON-LD structured data | -| `ThemeToggle.astro` | Light/dark/system theme switcher | -| `PageBreadcrumb.astro` | Accessible breadcrumb navigation | -| `PageHero.astro` | Standard hero section (tagline, h1 with accent word, subtitle, share, CTA slot) | -| `FeatureGrid.astro` | Reusable icon + title + description card grid (static or linked) | -| `CTASection.astro` | End-of-page call-to-action section (heading, description, button slot) | -| `MetricCard.astro` | Single stat/metric card (value + label) | -| `MetricGroup.astro` | Responsive grid container for MetricCard components | -| `ShareButton.astro` | Social sharing (Web Share API + fallback dropdown) | -| `BuilderBox.astro` | Builder UI pattern (dashed borders) | -| `InfoTooltip.astro` | Contextual help tooltip | -| `UserAvatar.astro` | Avatar with fallback initials | - -**React UI primitives** (from Radix UI, in `src/components/ui/`): - -| Component | Radix Primitive | Purpose | -|-----------|-----------------|---------| -| `accordion.tsx` | Accordion | Expandable content sections | -| `avatar.tsx` | Avatar | User avatars | -| `badge.tsx` | — | Status indicators, tags | -| `breadcrumb.tsx` | — | Breadcrumb (React version) | -| `button.tsx` | — | Button variants | -| `card.tsx` | — | Card container | -| `dialog.tsx` | Dialog | Modal dialogs | -| `dropdown-menu.tsx` | DropdownMenu | Dropdown menus | -| `input.tsx` | — | Form inputs | -| `label.tsx` | Label | Form labels | -| `navigation-menu.tsx` | NavigationMenu | Navigation menus | -| `scroll-area.tsx` | ScrollArea | Custom scroll containers | -| `separator.tsx` | Separator | Visual dividers | -| `tabs.tsx` | Tabs | Tab interfaces | -| `tooltip.tsx` | Tooltip | Tooltips | - -### Component rules - -1. **Components are stateless renderers**. They receive props and return markup. No page-specific logic. -2. **Components reference the styling layer** for visual appearance. Use design tokens (`var(--text)`, `var(--primary)`) and semantic typography classes (`text-body`, `text-meta`), never hardcoded values. -3. **Components are reusable**. If it appears on more than one page, extract it into a component. -4. **Components do not fetch data**. Data fetching happens at the page level (Layer 4) and is passed as props. -5. **New components must be accessible by default**: ARIA labels, keyboard navigation, focus management, touch targets. - ---- - -## Layer 3: Styling - -The styling layer is the single source of truth for how everything looks. It defines the design tokens, semantic classes, and responsive behavior that components and pages consume. - -### Architecture - -``` -src/styles/global.css - @import "tailwindcss" - @theme { ... } Design tokens (colors, fonts, spacing, sizes) - @layer base { ... } Theme variables, typography scale, HTML element defaults - @layer components { ... } Reusable component classes (.card, .btn-*, .text-body, etc.) - @layer utilities { ... } Custom utility classes (text colors, mineral colors, shadows) - Accessibility layer Skip links, focus, touch targets, reduced motion, print -``` - -### Design tokens (`@theme`) - -All visual values are defined as CSS custom properties in the `@theme` block. These are the single source of truth: - -- **Colors**: Five African Minerals palette, surface colors, text colors, semantic colors -- **Typography**: Font families, weights, and **text size scale tokens** -- **Spacing**: 4px base unit scale -- **Borders**: Radius tokens for buttons (12px), cards (16px), inputs (8px), badges (9999px) -- **Shadows**: Soft, single-layer shadow scale -- **Component tokens**: Button sizes, input sizes, nav dimensions - -### Semantic typography system - -**This is critical.** Text sizing is controlled centrally through semantic CSS classes defined in `@layer components`. Pages and components use these classes instead of raw Tailwind text sizing utilities. - -| Class | Desktop | Mobile | Line Height | Use For | -|-------|---------|--------|-------------|---------| -| `.text-body` | 20px | 18px | 1.7 | Paragraphs, descriptions, list items, card content, button text | -| `.text-body-lg` | 24px | 20px | 1.6 | Section subtitles, lead paragraphs, featured descriptions | -| `.text-meta` | 18px | 16px | 1.5 | Dates, labels, secondary info, table data, form hints | -| `.text-caption` | 16px | 16px | 1.4 | Badges, tags, very short decorative labels | - -These classes reference `@theme` tokens (`--text-size-body`, `--text-size-body-mobile`, etc.), so changing a single token value updates every instance across the site. - -**What you must use semantic classes for:** -- All paragraph and body text -> `text-body` -- Section introductions and subtitles -> `text-body-lg` -- Dates, labels, metadata -> `text-meta` -- Badge and tag text -> `text-caption` - -**What you must NOT do:** -- Use raw `text-sm`, `text-base`, `text-lg` for content text (these are for headings and exceptional cases only) -- Hardcode `font-size` values in scoped ` - - - -``` - -### Styling Approach - -The project uses **Tailwind CSS 4.1+** with custom `@theme` tokens. Both approaches are used: - -1. **Tailwind utility classes** - Preferred for page-level layout and common patterns -2. **Scoped ` -``` - -### TypeScript Conventions - -- **Strict mode** enabled via `tsconfig.json` -- **Explicit interfaces** for all component props -- **No `any` types** - use proper typing -- **Path aliases** - `@/*` maps to `./src/*` - -```typescript -// Good -interface Props { - variant?: 'primary' | 'secondary'; - items: string[]; -} - -// Bad -const data: any = fetchData(); -``` - -### Icon Usage - -**Always use Lucide icons. Never use emojis.** - -```astro ---- -import { BookOpen, Users, Globe } from '@lucide/astro'; ---- - -