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
3 changes: 2 additions & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"Bash(git push -f:*)",
"Read(**/.env)",
"Read(**/.env.*)"
]
],
"ask": ["Bash(git push:*)", "Bash(gh pr create:*)"]
}
}
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: CI

on:
pull_request:
push:
branches: [main]

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run format:check
- run: npm run typecheck
- run: npm test
- run: npm run build
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ CLAUDE.local.md
# Individual contribution summaries go to Canvas, never git
bidrag*
contributions*

# Build/tool output
*.log
*.local
8 changes: 8 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "oxc"],
"rules": {
"react/rules-of-hooks": "error",
"react/only-export-components": ["warn", { "allowConstantExport": true }]
}
}
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist
coverage
package-lock.json
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"semi": false,
"singleQuote": true
}
41 changes: 34 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,46 @@ Fixed by the assignment:
- State management: a real library (Apollo local state, Redux, Zustand...)
- Backend: our own GraphQL server in Node + TypeScript
- Tests: Vitest for components, automated end-to-end tests, API tests
- ESLint + Prettier
- Linting (we use oxlint, the Vite template default) + Prettier

Chosen:

- GraphQL server: Apollo Server (standalone, port 3001)
- Backend runs TypeScript directly with Node's type stripping (Node >= 22.18): no build step,
so only erasable TS syntax (no `enum`, no `namespace`), and imports use the `.ts` extension

Still open (record the decision here and in README.md when made):

- Dataset / domain: TBD
- Database: TBD
- GraphQL server lib, client lib, E2E tool, UI/component lib: TBD
- GraphQL client / state lib, E2E tool, UI/component lib: TBD

## Repo layout

Not scaffolded yet. Planned: `frontend/` (Vite app) and `backend/` (GraphQL
server), each with its own `package.json`. Update this section when it exists,
including the exact install/dev/test/lint commands.
npm workspaces, one `npm install` at the root.

```
frontend/ Vite + React app (src/App.tsx, tests next to components as *.test.tsx)
backend/ Apollo GraphQL server
src/schema.ts typeDefs + resolvers
src/server.ts createServer(), used by index.ts and by tests
src/index.ts starts the server on PORT (default 3001)
docs/ assignment text
```

Commands (run from the root):

| Command | What |
| -------------------------------------- | --------------------------------- |
| `npm run dev:frontend` / `dev:backend` | Dev servers (Vite / node --watch) |
| `npm test` | Vitest in both workspaces |
| `npm run lint` | oxlint |
| `npm run format` / `format:check` | Prettier |
| `npm run typecheck` | tsc in both workspaces |
| `npm run build` | Frontend production build |

CI (`.github/workflows/ci.yml`) runs lint, format:check, typecheck, test and build on every PR.
Add dependencies to a workspace: `npm i <pkg> -w frontend` (or `-w backend`).

## How we work (GitHub flow, `main` is protected)

Expand All @@ -58,10 +85,10 @@ Every member needs `gh auth login --hostname git.ntnu.no` once.
## Rules for the agent

- Before starting work, read the linked issue (`gh issue view <n>`) and stay within its scope. Suggest a new issue for anything extra instead of doing it.
- Do not commit/push/open PRs unless the user asks. Never push to `main`.
- **Commit locally, never push on your own.** Finished work is committed on the local branch only. The user reviews the code first; push or open a PR only when the user explicitly asks for it in the current conversation. Never push to `main`.
- **AI use must be documented** (course requirement). Every PR has an "AI usage" section: say which parts were AI-generated/assisted and what the human checked or changed. Be honest and specific.
- Keep AI attribution minimal: at most one `Co-Authored-By` trailer per commit, no extra mentions in code, comments or issue text.
- Code is read by external graders: clear names, small components, comments that explain *why* (not what). Every non-obvious decision gets a short comment or a README note.
- Code is read by external graders: clear names, small components, comments that explain _why_ (not what). Every non-obvious decision gets a short comment or a README note.
- Accessibility by default: semantic HTML, labels on inputs, keyboard navigation, visible focus, alt text, sufficient contrast, `aria-live` for result updates.
- Sustainability by default: fetch only what is shown (GraphQL field selection + pagination), cache, no heavy deps for small things, compress/lazy-load images, respect `prefers-reduced-motion`, support dark mode.
- Tests come with features: component test for UI logic, API test for resolvers. Run lint + tests before saying something is done.
Expand Down
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,23 @@ Before a pull request can be merged:
request, and approvals from staff do not count — you review each other's
code.
2. **Every review comment must be marked resolved.** Reply to each one, fix it
or explain why not, then click *Resolve conversation*.
or explain why not, then click _Resolve conversation_.
3. **Pushing new commits dismisses the approval**, so if you change something
after being approved, ask for another review. This is deliberate: what was
approved is what gets merged.

## Getting started

Requires Node.js 22.18 or newer.

```bash
npm install
npm run dev:backend # GraphQL server on http://localhost:3001
npm run dev:frontend # Vite dev server, open the printed URL (…/project2/)
```

Checks: `npm test`, `npm run lint`, `npm run format:check`, `npm run typecheck`.

## Team workflow

Issue → branch `<type>/<issue>-slug` → PR with `Closes #<issue>` → review → merge.
Expand Down
22 changes: 22 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "backend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "node --watch src/index.ts",
"start": "node src/index.ts",
"typecheck": "tsc",
"test": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"@apollo/server": "^5.5.1",
"graphql": "^16.14.2"
},
"devDependencies": {
"@types/node": "^24.13.6",
"typescript": "^6.0.3",
"vitest": "^5.0.2"
}
}
8 changes: 8 additions & 0 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { startStandaloneServer } from '@apollo/server/standalone'
import { createServer } from './server.ts'

const port = Number(process.env.PORT ?? 3001)
const { url } = await startStandaloneServer(createServer(), {
listen: { port },
})
console.log(`GraphQL server ready at ${url}`)
12 changes: 12 additions & 0 deletions backend/src/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Placeholder schema: replaced once the dataset is chosen.
export const typeDefs = `#graphql
type Query {
hello: String!
}
`

export const resolvers = {
Query: {
hello: () => 'Hello from T13',
},
}
10 changes: 10 additions & 0 deletions backend/src/server.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import assert from 'node:assert'
import { expect, test } from 'vitest'
import { createServer } from './server.ts'

test('hello query', async () => {
const res = await createServer().executeOperation({ query: '{ hello }' })
assert(res.body.kind === 'single')
expect(res.body.singleResult.errors).toBeUndefined()
expect(res.body.singleResult.data).toEqual({ hello: 'Hello from T13' })
})
6 changes: 6 additions & 0 deletions backend/src/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ApolloServer } from '@apollo/server'
import { resolvers, typeDefs } from './schema.ts'

export function createServer() {
return new ApolloServer({ typeDefs, resolvers })
}
18 changes: 18 additions & 0 deletions backend/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
// Node runs the .ts files directly (type stripping), so tsc only type-checks.
"compilerOptions": {
"target": "es2023",
"module": "nodenext",
"moduleResolution": "nodenext",
"types": ["node"],
"strict": true,
"noEmit": true,
"skipLibCheck": true,
"verbatimModuleSyntax": true,
"erasableSyntaxOnly": true,
"allowImportingTsExtensions": true,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": ["src"]
}
13 changes: 13 additions & 0 deletions frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="%BASE_URL%favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>T13 Project 2</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
30 changes: 30 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
"test": "vitest run",
"test:watch": "vitest",
"typecheck": "tsc -b"
},
"dependencies": {
"react": "^19.2.8",
"react-dom": "^19.2.8"
},
"devDependencies": {
"@testing-library/jest-dom": "^7.0.1",
"@testing-library/react": "^16.3.3",
"@types/node": "^24.13.3",
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.7",
"@vitejs/plugin-react": "^6.1.1",
"jsdom": "^30.1.1",
"typescript": "~6.0.2",
"vite": "^8.3.0",
"vitest": "^5.0.2"
}
}
1 change: 1 addition & 0 deletions frontend/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react'
import { expect, test } from 'vitest'
import App from './App'

test('renders the main heading', () => {
render(<App />)
expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument()
})
9 changes: 9 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function App() {
return (
<main>
<h1>T13 Project 2</h1>
</main>
)
}

export default App
22 changes: 22 additions & 0 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* Base styles. System fonts only: no web font downloads (green web). */
:root {
color-scheme: light dark;
font-family: system-ui, 'Segoe UI', Roboto, sans-serif;
line-height: 1.5;
}

body {
margin: 0;
}

:focus-visible {
outline: 3px solid Highlight;
outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
}
10 changes: 10 additions & 0 deletions frontend/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
5 changes: 5 additions & 0 deletions frontend/src/test-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import '@testing-library/jest-dom/vitest'
import { cleanup } from '@testing-library/react'
import { afterEach } from 'vitest'

afterEach(cleanup)
Loading
Loading