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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: exploretech.la Continuous Integration

# Verifies every pull request targeting master. Runs the same install, test,
# image-manifest check (via prebuild) and production build that deployment uses,
# without any access to deployment secrets.
# Verifies every pull request targeting master. Runs the same install, checks,
# image-manifest check (via prebuild) and production build that deployment
# uses, plus the browser smoke suite, without any access to deployment secrets.
on:
pull_request:
branches: [master]
Expand All @@ -20,7 +20,7 @@ concurrency:
jobs:
verify:
runs-on: ubuntu-latest
timeout-minutes: 20
timeout-minutes: 25

steps:
- name: Check out the repository
Expand All @@ -38,10 +38,46 @@ jobs:
- name: Install packages
run: npm ci

- name: Check types
run: npm run typecheck

- name: Lint
run: npm run lint

- name: Check formatting
run: npm run format:check

# References between people, teams and event content, and the assets
# they point at. Loads the content modules; makes no network requests.
- name: Check content
run: npm run content:check

- name: Run the tests
run: npm test

# prebuild runs npm run images:check, so the generated image map is
# verified against scripts/image-sources.json before bundling.
- name: Build the site
run: npm run build

# Only Chromium: the smoke suite gates behaviour, not cross-engine
# rendering. Browsers are not in the npm cache, so this is a real
# download on every run.
- name: Install the Chromium test browser
run: npx playwright install --with-deps chromium

# Serves the build through scripts/serve-built-site.cjs, which reproduces
# GitHub Pages' 404-to-root restoration.
- name: Run the browser smoke suite
run: npm run test:browser

- name: Upload the browser smoke report
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: playwright-report
path: |
playwright-report
test-results
retention-days: 7
if-no-files-found: ignore
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

# testing
/coverage
/playwright-report
/test-results

# production
/build
Expand Down
9 changes: 9 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules/
build/
handoffs/
playwright-report/
test-results/
src/static/
src/constants/optimizedImages.ts
src/image.png
package-lock.json
170 changes: 130 additions & 40 deletions README.md

Large diffs are not rendered by default.

70 changes: 70 additions & 0 deletions docs/refactor-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Contributor refactor plan and acceptance ledger

## Mission

Centralize people, role assignments, and annual event content; organize typed feature renderers; replace the old runtime and styling dependencies without redesigning the site. No CMS, backend, database, hosting migration, deployment, merge, branch-protection change, or secret change is included.

Baseline: `origin/master` at `f435aca007fbaf3bc2726c8239bdd784d56b8d6d`, the independently verified PR #107 merge. Work started on `refactor-contributor-architecture`, not the already-merged modernization branch. The Node/Vite/Vitest modernization was retained.

## Implementation sequence and decisions

1. Fetch and verify master, preserve the user-owned image and local handoffs, build an immutable baseline, then capture screenshots and geometry before changing CSS.
2. Establish shared profile/role/event types, content ownership, brand tokens, and the observed breakpoint/reset contract. Parallel feature writers owned disjoint files; integration owned runtime, navigation, analytics, and configuration.
3. Centralize all 35 people in `content/people.ts`. The seven team sections contain 45 contextual assignments; `content/speakers.ts` contains eight historical speaking roles. Names, portraits, links, titles, order, and missing-photo slots remain unchanged.
4. Consolidate annual resources into `content/events/<year>.ts` plus shared renderers. `/resources` deliberately retains its 2022 page content and "exploretech 2021" menu label. Registration remains the distinct 2021 two-day event. `EVENT_ROUTES` drives both routing and navigation; its keys determine valid event years.
5. Upgrade to stable React/DOM 19.3.0, Router 7.18.3, and Tailwind 4.3.3. TypeScript 6.0.3 is the latest stable compatible with typescript-eslint 8.70.0; TypeScript 7 is outside that linter's peer range. No forced peer resolution is used.
6. Replace Bootstrap's generic class API with the small `action`, `site-nav`, `site-menu`, `content-card`, and `resource-list` canon. Layout and dimensions use exact-value Tailwind utilities. Motion and state effects remain explicit CSS. Preflight is excluded. The former Sass cascade's missing tablet rules and width overrides were preserved rather than corrected into a redesign.
7. Remove old JS/JSX, Sass, copied annual components, unused content/dependencies, classic-JSX compatibility configuration, and unused scaffold files. Retain every image-generation input and public document.
8. Extend the existing CI job, add content checks and durable browser smoke, document contributor tasks and the [UI canon](ui-canon.md), then perform independent review and final verification.

## Acceptance ledger

| Requirement | State | Evidence |
| --- | --- | --- |
| Latest master and fresh branch | done | Fetch, branch creation, GitHub PR #107 merge verification |
| Baseline screenshots and geometry | done | 210 baseline cells at 14 routes and 15 widths, including breakpoint-adjacent pixels |
| One person profile and contextual assignments | done | `content/people.ts`, `teams.ts`, `speakers.ts`; 35 profiles, 53 total roles; content and browser checks |
| Shared annual data/rendering and archives | done | `content/events`, `features/events`, `features/registration`; preserved route text, image order, links and schedules |
| Feature and content ownership | done | README ownership table and edit-person/roster/event/image instructions |
| Strict TS and compatible stable runtime | done | No application JS/JSX; strict typecheck, lint, cold Vite startup and live JSX HMR without document reload |
| Tailwind cutover and small shared UI canon | done | No Bootstrap/React Bootstrap/Sass dependencies or application imports; semantic selectors, explicit reset/tokens, shared ActionLink/OutboundLink/Collapse |
| Proven dead code removed | done | Unreachable annual copies, inactive components, old constants, all Sass files and unused scaffold removed; original assets retained |
| Shipped image loading and regressions preserved | done | 12 Vitest tests, including the original failed/stale carousel and analytics assertions; browser slow/failing-image cases |
| Public binary URLs and bytes retained | done | `asset-parity.cjs`: all 102 public image/PDF/icon URLs and SHA-256 hashes unchanged; `404.html` and `CNAME` byte-identical |
| CI content checks and browser smoke | done | Existing read-only `verify` extended with types, lint, formatting, content, image/build and 34 Chromium cases; no deployment secrets |
| Contributor documentation | done | README, UI canon, this ledger, committed parity summary and comparison images |
| Clean install/types/lint/format/tests/images/build | done | `npm ci`, all listed checks, production build; npm audit reports zero vulnerabilities at verification time |
| Responsive and interaction parity | done | 210 final geometry/style/content cells; 34 WebKit plus 34 Firefox scenarios; 25 hover/focus/video/carousel states; autoplay, pause/resume, interrupted menu and keyboard probes |
| Analytics semantics retained | done | Dummy-ID build with real retained `analytics.js`: one tracker/script, resolved-only alias pageview, query changes, no hash-only pageview, correct outbound attribution; collection blocked |
| Independent findings resolved | done | Runtime and content/style reviews, followed by targeted approvals after the two fixes below |
| Reviewable pull request without merge/deploy | done | [PR #108](https://github.com/exploretech-la/website/pull/108) is open against master; no merge or deployment performed |

## Demonstrated regressions and fixes

- Tailwind's `collapse` utility initially hid open FAQ answers. Renaming the component's state classes to `disclosure` and `disclosure-transition` removed the collision without an override or compatibility layer.
- The first navigation implementation wrapped dropdown keyboard selection. The baseline clamps at the first/last item and does not open on ArrowUp. A new browser regression failed against that candidate build and passes after the correction; the ten-step baseline/candidate keyboard sequences now match.
- Independent runtime review found that the Back-navigation test replaced its spy array while the spy retained the old one. The test now clears the array in place and first proves it observed pushed-route scrolling. Targeted re-review approved the fix.
- Independent content review found a registration schedule variant that was accepted by types but not rendered. The unused union and conditional were removed; registration now requires and always renders its two-day tuple. Targeted re-review approved the fix.

## Verification results and limits

Machine-readable results: [verification/refactor-parity.json](verification/refactor-parity.json).

- Final geometry matrix: 210/210 passed. Maximum observed difference is 0.03125 CSS pixels against a 0.05px ceiling. Text, destinations, image order/loading attributes, and the sampled computed styles compare exactly. Implementation class names are recorded but not treated as a visual contract.
- Final PNG comparison: 210 pairs, no dimension changes, 121 byte-identical pairs, 142 with no flagged pixels. The largest flagged fraction is 0.005645%, using pixelmatch threshold 0.1 with antialiasing excluded. Screenshots are not universally pixel-identical. Sampled residual differences are in text rasterization; diff bounds and counts are in the committed summary.
- All 25 measured hover/focus/video/carousel states match. Slide motion has observed intermediate transforms and the same 600ms easing. Autoplay advances, pauses on hover, and resumes on both baseline and candidate. Interrupted menu operations settle correctly; intermediate samples depend on frame scheduling.
- WebKit and Firefox each pass 34 scenarios, including actual Back reading-position restoration. Chromium's durable test separately proves that the app does not override POP scrolling.
- CSS gzip changed from 28.23KB to 8.41KB after removing the unused resource-list active states. Application JS gzip changed from 99.60KB to 112.98KB with the newer runtime. These are build sizes, not field-performance claims.
- Browser viewports are emulated. Playwright WebKit is not native Safari or a physical iPhone. External links are syntax-checked, not all contacted. Analytics testing proves local vendor processing, not production-property ingestion.

Before is on the left and after is on the right in these representative, unscaled viewport crops:

- [Phone team comparison](verification/team-phone.png)
- [Tablet home comparison](verification/home-tablet.png)
- [Desktop resources comparison](verification/resources-desktop.png)

The full local screenshot/JSON set and nonzero pixel-diff images remain under `/tmp/exploretech-refactor-baseline` and `/tmp/exploretech-refactor-candidate`. The README documents rerunning the capture and asset comparisons. Temporary migration and diagnostic scripts are not application dependencies.

## Protected state

`src/image.png` remains untracked and unchanged, with SHA-256 `901b37768de40e95b2718c8a350f5761d6d341f02f2e23c9337164ad95f5ae3b`. The local `/handoffs/` exclusion remains effective. Deployment workflow, hosting, secrets, and branch protection are unchanged.
26 changes: 26 additions & 0 deletions docs/ui-canon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# UI canon

This is a styling-system migration, not a redesign. `src/styles/theme.css` is the only stylesheet entry. It loads Tailwind's theme and utilities, then explicit base and component layers. Preflight is excluded because it changes the site's heading, image, list, and button geometry.

## Shared controls

- `ActionLink` renders a native anchor and preserves the existing Space-key behavior of action links. Use `action` with one of the site's five existing color treatments: `action-info`, `action-primary`, `action-warning`, `action-outline`, or `action-inverse`. `action-large` is the existing large size. The definitions live in `styles/controls.css`; do not add another button palette in a feature.
- `OutboundLink` owns outbound analytics, email redaction through the analytics module, and same-window navigation completion. Its props are native anchor props plus `eventLabel`.
- `Collapse` owns the menu and FAQ height transition. `disclosure` and `disclosure-transition` avoid Tailwind's unrelated `collapse` visibility utility. The 350ms CSS transition and the previously shipped 300ms fallback remain distinct on purpose.
- `Header` owns the single navigation layout and dropdown behavior. `site-nav-*` and `site-menu-*` are internal selectors, not another component library.
- `content-card-*` and `resource-list-*` supply the shared static card structure. Features compose native elements rather than passing variant props through wrappers.
- `People` and `YoutubeEmbed` own reserved media geometry and loading behavior. The home carousel owns its slide classes and timing separately.

## Values and breakpoints

`styles/tokens.css` owns the brand colors and section spacing. Breakpoints are 576, 768, 992, and 1200px, not Tailwind's defaults. Existing feature queries include 575.98px and an inclusive 768px mobile team rule; those boundaries remain deliberate.

Layout, sizing, spacing, and typography use Tailwind utilities through `@apply`. Exact existing values use arbitrary utilities where a default would change geometry. Motion, gradients, pseudo-elements, and state-specific effects remain ordinary CSS. Group utilities only when their properties do not override one another.

The base reset and some control values derive from the former Bootstrap dependency. Their license is retained in `licenses/bootstrap.txt`; Bootstrap itself, its generic class API, React Bootstrap, and Sass are removed.

## Verify a change

Run the type, lint, content, image, build, and browser checks listed in the README. Compare phone, tablet, desktop, and both sides of affected breakpoints. A screenshot with motion disabled does not prove an animation: exercise the transition separately, including keyboard focus and interrupted open/close actions.

The parity tool stores raw geometry, computed styles, content, links, image attributes, and screenshots. Its comparison ignores implementation class names and permits at most 0.05 CSS pixels of geometry rounding. It does not ignore differences in text, destinations, image order, loading policy, or computed style values.
Binary file added docs/verification/home-tablet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading