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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ When Codex substantially authors a Git commit in this repository:

## Project

Gapwise is a privacy-first web application for students at 12 supported Canadian universities, served from `https://gapwise.ca` and dedicated university subdomains. Timetable identity and source-backed building maps cover all supported editions. Pedestrian routing and entrance coverage vary by campus; UTM-specific places and ACORN import remain scoped to the University of Toronto edition.
Gapwise is a privacy-first web application for students at 13 supported Canadian universities, served from `https://gapwise.ca` and dedicated university subdomains. Timetable identity and source-backed building maps cover all supported editions. Pedestrian routing and entrance coverage vary by campus; UTM-specific places and ACORN import remain scoped to the University of Toronto edition.

The current product:

Expand Down
2 changes: 1 addition & 1 deletion docs/CAMPUS_DATA.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Campus data and documentation boundaries

Canonical public multi-university campus facts, geometry, provenance, and data-maintenance guidance live in [`GapwiseHQ/data`](https://github.com/GapwiseHQ/data). The dataset covers 12 universities (University of Toronto, Carleton University, Toronto Metropolitan University, Queen's University, Wilfrid Laurier University, York University, McMaster University, Western University, University of Guelph, University of Ottawa, Brock University, and the University of British Columbia) across 14 campus models.
Canonical public multi-university campus facts, geometry, provenance, and data-maintenance guidance live in [`GapwiseHQ/data`](https://github.com/GapwiseHQ/data). The dataset covers 13 universities (University of Toronto, Carleton University, Toronto Metropolitan University, Queen's University, Wilfrid Laurier University, York University, McMaster University, Western University, University of Guelph, University of Ottawa, Brock University, the University of British Columbia, and the University of Waterloo) across 15 campus models.

Public human-readable data documentation lives at **https://docs.gapwise.ca/data/**. The currently published raw distributions live at **https://data.gapwise.ca/datasets/**.

Expand Down
2 changes: 1 addition & 1 deletion docs/ECOSYSTEM_INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Gapwise is one product ecosystem implemented across seven product repositories a

| Repository | Owns | Consumes |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------- |
| `data` | Canonical multi-university campus facts and geometry across 12 universities; routing graphs and reviewed entrances | Reviewed public/field evidence; core consumer tests |
| `data` | Canonical multi-university campus facts and geometry across 13 universities; routing graphs and reviewed entrances | Reviewed public/field evidence; core consumer tests |
| `gapwise` | Web/PWA across 11 university editions, timetable/gap/routing semantics, encrypted private state, API v1, OpenAPI and both SDKs | Validated build-time Data mirror; explicit AI delegation |
| `android` | Kotlin/Compose UX, Keystore persistence, native import and encrypted-sync adapter (focuses on U of T) | Core HTTP/crypto contracts and public campus APIs |
| `ios` | SwiftUI UX and portable local timetable foundation (focuses on U of T) | Campus-scoped identity; broader native campus/account integrations remain pending |
Expand Down
2 changes: 1 addition & 1 deletion docs/INFRASTRUCTURE_COST_AND_SCALING_MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

Gapwise operates on an intensely disciplined **local-first, zero-unnecessary-cloud architecture**. Course schedules, `.ics` files, calendar parsing, timetable rendering, walking route graphs, gap detection, and campus building spatial indexes execute 100% client-side in the student's browser (WebAssembly and Web Crypto).

Because of this architectural posture, **Gapwise's current operational infrastructure cost is $0.00/month across all 12 supported universities**.
Because of this architectural posture, **Gapwise's current operational infrastructure cost is $0.00/month across all 13 supported universities**.

This document models the resource consumption, provider limits, financial projections, and explicit upgrade triggers across four growth stages:

Expand Down
84 changes: 84 additions & 0 deletions e2e/waterloo-university.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { expect, test } from "@playwright/test";
import { readFileSync } from "node:fs";
import { watchForAppFailures } from "./helpers";

const questFixture = readFileSync(
new URL("../tests/fixtures/waterloo-quest-sanitized.tsv", import.meta.url),
"utf8",
);

test("Waterloo Quest import preserves the canonical campus through timetable and routing", async ({
page,
baseURL,
}) => {
test.skip(test.info().project.name !== "chromium");
if (!baseURL) throw new Error("Playwright baseURL is required");
const failures = watchForAppFailures(page, baseURL);

await page.goto("/?university=waterloo&campus=waterloo-main");
await expect(page.getByText("For University of Waterloo", { exact: true })).toBeVisible();
await expect(page.locator(".brand-scope-pill").first()).toHaveText("Waterloo");

await page.getByRole("button", { name: "Paste Quest Class Schedule rows" }).click();
await page
.getByRole("textbox", { name: "Paste schedule text from Quest Class Schedule" })
.fill(questFixture);
await page.getByRole("button", { name: "Import pasted schedule" }).click();

await expect(page).toHaveURL(/\/today$/);
await page
.getByRole("group", { name: "View mode" })
.getByRole("button", { name: "Weekly timetable" })
.click();
await expect(page.getByText("MATH 135").first()).toBeVisible();
await expect(page.getByText("CS 135").first()).toBeVisible();
await expect(page.locator(".brand-scope-pill").first()).toHaveText("Waterloo");

await page
.getByRole("group", { name: "View mode" })
.getByRole("button", { name: "Day route" })
.click();
await page
.getByRole("group", { name: "Route weekday" })
.getByRole("button", { name: "Monday" })
.click();
await expect(page.getByRole("searchbox", { name: "Search Waterloo buildings" })).toBeVisible();
await expect(page.locator(".map-time-marker")).toHaveCount(2);

const search = page.getByRole("searchbox", { name: "Search Waterloo buildings" });
await search.fill("MC");
await expect(page.getByTestId("building-search-result").first()).toContainText(
"Mathematics & Computer",
);

await page.reload();
await expect(page.locator(".brand-scope-pill").first()).toHaveText("Waterloo");
await expect(page.getByRole("searchbox", { name: "Search Waterloo buildings" })).toBeVisible();

await page.goto("/route?university=waterloo&campus=waterloo-main");
await expect(page.locator(".brand-scope-pill").first()).toHaveText("Waterloo");
await expect(page.getByRole("searchbox", { name: "Search Waterloo buildings" })).toBeVisible();
failures.assertClean();
});

test("Waterloo has its own demo timetable and usable mobile campus map", async ({
page,
baseURL,
}) => {
test.skip(test.info().project.name !== "mobile-chromium");
if (!baseURL) throw new Error("Playwright baseURL is required");
const failures = watchForAppFailures(page, baseURL);

await page.goto("/?university=waterloo&campus=waterloo-main");
await expect(page.getByText("For University of Waterloo", { exact: true })).toBeVisible();
await page.getByRole("button", { name: "Try a demo" }).click();
await expect(page).toHaveURL(/\/timetable/);
await page.getByRole("group", { name: "Weekday" }).getByRole("button", { name: /Mon/ }).click();
await expect(page.getByText("MATH 135").first()).toBeVisible();
await expect(page.getByText("CS 135").first()).toBeVisible();

await page.getByRole("navigation", { name: "Main" }).getByRole("link", { name: "Map" }).click();
await expect(page.getByRole("searchbox", { name: "Search Waterloo buildings" })).toBeVisible();
await expect(page.getByRole("button", { name: "Options" })).toBeVisible();
failures.assertClean();
});
2 changes: 1 addition & 1 deletion public/institutional-brief.html
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ <h1 class="brand-title">
bridges the gap between official course schedules and physical campus navigation. It
connects class times to campus geometry—computing true usable break times, verified walking
paths, step-free navigation, and study destinations across
<strong>12 Canadian universities and 14 campus models</strong> with strict
<strong>13 Canadian universities and 15 campus models</strong> with strict
privacy-by-design.
</div>

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/universities/waterloo/favicon-16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/universities/waterloo/favicon-32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/universities/waterloo/icon-192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/universities/waterloo/icon-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions public/universities/waterloo/logo-mark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/universities/waterloo/og-card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions public/universities/waterloo/site.webmanifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "Gapwise — University of Waterloo",
"short_name": "Gapwise",
"icons": [
{ "src": "/universities/waterloo/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/universities/waterloo/icon-512.png", "sizes": "512x512", "type": "image/png" }
],
"theme_color": "#0d1117",
"background_color": "#0d1117",
"display": "standalone",
"start_url": "/"
}
8 changes: 8 additions & 0 deletions scripts/generate-social-cards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ const CARDS: CardConfig[] = [
tagline: "Make the time between classes count.",
detail: "University of British Columbia",
},
{
outputPath: "public/universities/waterloo/og-card.png",
accentColor: "#D6A800",
textColor: "#F8D347",
title: "Gapwise",
tagline: "Make the time between classes count.",
detail: "University of Waterloo",
},
// ── Root fallbacks (U of T default) ──────────────────────
{
outputPath: "public/og-card.png",
Expand Down
1 change: 1 addition & 0 deletions src/components/MarketingLandingImpl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const INSTITUTION_MARKETING_METRICS: Record<
uottawa: { count: 17, sampleCodes: ["UCU", "TBT", "MRT", "DMS"] },
brock: { count: 12, sampleCodes: ["ST", "TH", "MC", "GSB"] },
ubc: { count: 236, sampleCodes: ["ICCS", "BUCH", "PHRM", "LIFE"] },
waterloo: { count: 68, sampleCodes: ["MC", "DC", "QNC", "AL"] },
};

export function MarketingLandingImpl({
Expand Down
2 changes: 1 addition & 1 deletion src/content/public-feature-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const PUBLIC_FEATURE_PAGES = {
},
{
title: "Made for multiple universities",
body: "Timetable identity and source-backed building maps are supported for all twelve universities. Pedestrian routing, verified entrances, and campus places vary by campus and are shown only where supported.",
body: "Timetable identity and source-backed building maps are supported for all thirteen universities. Pedestrian routing, verified entrances, and campus places vary by campus and are shown only where supported.",
},
],
},
Expand Down
16 changes: 10 additions & 6 deletions src/data/campuses/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,12 +339,16 @@ export function ensureCampusCatalog(campusId: string | null | undefined): Promis
if (!campusId || universityCatalogs[campusId] || campusId === "utm") return Promise.resolve();
const existing = campusCatalogLoads.get(campusId);
if (existing) return existing;
const load =
campusId === "ubc-vancouver"
? import("./ubc/catalog.json?raw").then((module) => {
registerUniversityCatalog(campusId, JSON.parse(module.default) as UniversityCatalog);
})
: Promise.resolve();
const catalogLoaders: Record<string, () => Promise<{ default: string }>> = {
"ubc-vancouver": () => import("./ubc/catalog.json?raw"),
"waterloo-main": () => import("./waterloo/catalog.json?raw"),
};
const loader = catalogLoaders[campusId];
const load = loader
? loader().then((module) => {
registerUniversityCatalog(campusId, JSON.parse(module.default) as UniversityCatalog);
})
: Promise.resolve();
campusCatalogLoads.set(campusId, load);
return load;
}
Expand Down
Loading
Loading