Compare commits

..

107 Commits

Author SHA1 Message Date
kschappell 407979f643 use central cloudflared tunnel
CI / Backend (push) Failing after 13s
CI / Frontend (push) Failing after 1m1s
CI / Docker Build (push) Has been skipped
2025-09-14 20:33:15 +01:00
kschappell ab2b5b065d uk spelling: visualisation 2025-09-14 19:58:37 +01:00
kschappell 01e971cd4e uk spelling: labelled 2025-09-14 19:36:14 +01:00
kschappell ed2991a092 fix: API_URL env for server-side fetches 2025-09-14 19:24:31 +01:00
kschappell bb23c87f78 fix(docker): API URL as build arg 2025-09-14 18:25:50 +01:00
kschappell 935b26b0f7 fix: empty public folder in frontend build 2025-09-14 17:28:26 +01:00
kschappell 4f54ac29a3 update changelog 2025-09-12 16:29:54 +01:00
kschappell b576af260e viz primitives for data structures 2025-09-12 15:26:17 +01:00
kschappell 3b4c242f8d feat(viz): trie visualisation 2025-09-12 15:08:18 +01:00
kschappell 2446d7d5e7 chore(deploy): prod deploy config 2025-09-12 14:57:04 +01:00
kschappell 520e0a5980 learning progression + completion tracking 2025-09-12 14:12:38 +01:00
kschappell 47d75c32a9 feat(progress): spaced repetition dashboard 2025-09-12 13:15:06 +01:00
kschappell df31b08ee2 feat(content): pattern comparisons 2025-09-10 18:46:50 +01:00
kschappell a99ee0ea97 migrate 392 questions to pattern format 2025-09-10 18:05:55 +01:00
kschappell e805ed9b05 feat(patterns): pattern taxonomy + is_optimal 2025-09-08 16:03:14 +01:00
kschappell d25d71b7a6 fix: single number → bit-manipulation pattern 2025-09-08 15:49:57 +01:00
kschappell 4a053e9972 feat(viz): wire up bit manipulation 2025-09-08 14:50:57 +01:00
kschappell 5573b7f176 feat(content): bit manipulation pattern 2025-09-08 14:46:22 +01:00
kschappell 45cba588e9 single number algorithm def 2025-09-08 14:00:13 +01:00
kschappell f986d6a2f9 feat(viz): bit manipulation algorithm component 2025-09-08 13:32:40 +01:00
kschappell a4045dccc1 feat(viz): bit manipulation view 2025-09-04 00:36:55 +01:00
kschappell a5d1694917 feat(viz): bit display primitive 2025-09-04 00:22:32 +01:00
kschappell d361a1b94f feat(viz): bit manipulation types 2025-09-03 23:27:24 +01:00
kschappell d480e9c356 feat(viz): union-find 2025-09-03 22:44:24 +01:00
kschappell c30249de2f feat(viz): backtracking, greedy, intervals, matrix 2025-09-03 21:47:33 +01:00
kschappell 283d1b3848 feat(viz): heap pattern with kth largest 2025-09-03 21:30:46 +01:00
kschappell 85235c7277 feat(viz): dp coin change 2025-09-01 21:29:23 +01:00
kschappell 36233e943d feat(viz): tree/BFS/DFS patterns 2025-09-01 20:49:11 +01:00
kschappell 54c3ef373a fix viz layout stability 2025-08-24 17:43:46 +01:00
kschappell e6ca65e039 feat(viz): monotonic stack viz 2025-08-24 17:32:27 +01:00
kschappell 506d22a98f linked list visualisations 2025-08-24 16:33:51 +01:00
kschappell 777da6c186 feat(viz): sprint 1 - array visualisations 2025-08-24 16:09:24 +01:00
kschappell 241489f1bd feat(viz): two pointers narrative 2025-08-24 15:30:46 +01:00
kschappell 2d581ab14f feat(viz): interactive algorithm viz system 2025-08-23 20:28:22 +01:00
kschappell 7f330c7cbb feat(patterns): strategy tutorials 2025-08-23 19:58:33 +01:00
kschappell a322cb9a3b feat(patterns): data structure tutorials 2025-08-23 19:25:47 +01:00
kschappell a1f301d11b feat(patterns): pointer/array tutorials 2025-08-18 22:15:43 +01:00
kschappell a818e5bae7 feat(patterns): graph/tree traversal tutorials 2025-08-18 22:00:08 +01:00
kschappell ed4342d003 feat(patterns): tutorial system 2025-08-07 00:41:51 +01:00
kschappell 2929b8d569 fix(frontend): make stats pages dynamic 2025-08-07 00:07:22 +01:00
kschappell 2f5e1ced7c update changelog 2025-08-06 23:54:33 +01:00
kschappell 2c86e66a0e feat(content): hidden test cases 2025-08-06 23:21:42 +01:00
kschappell 71901d045a seo: sitemap, robots, og tags 2025-08-06 22:38:22 +01:00
kschappell 276df85bcc feat(frontend): big O complexity estimator 2025-07-31 15:05:41 +01:00
kschappell df611d26c3 fix(backend): CORS and API URL fixes 2025-07-31 14:52:27 +01:00
kschappell 4b04248f57 submit ux improvements 2025-07-31 13:53:39 +01:00
kschappell 20da63926d feat(frontend): add progress tracking 2025-07-31 13:23:11 +01:00
kschappell 323a1aa50e feat(content): function signatures + test cases 2025-07-13 19:53:34 +01:00
kschappell eb7c731af0 nit 2025-07-13 19:36:07 +01:00
kschappell ccc50c4c88 feat(content): more test cases 2025-07-13 19:25:39 +01:00
kschappell 2021de37b4 update .env.example and docker config 2025-06-27 20:52:16 +01:00
kschappell 192094e714 fix(frontend): handle API errors gracefully 2025-06-27 20:27:54 +01:00
kschappell d215538799 remove duplicate question file 2025-06-20 16:05:43 +01:00
kschappell f9740f3f69 feat(content): add new patterns 2025-06-20 16:02:40 +01:00
kschappell 7ee30b7165 extend complexity fields to text 2025-06-20 15:10:37 +01:00
kschappell 85e5470794 bump frontend deps 2025-06-20 14:40:28 +01:00
kschappell 01bdde9b17 feat(frontend): markdown and detail components 2025-06-20 13:53:44 +01:00
kschappell b4acd5eae2 vitest setup + component tests 2025-05-30 21:16:27 +01:00
kschappell 079c776e70 loading skeletons 2025-05-30 20:34:31 +01:00
kschappell 980b97290a fix docker build missing readme 2025-05-30 20:00:49 +01:00
kschappell e5f935909e feat(frontend): add question search 2025-05-30 19:27:31 +01:00
kschappell 7c7e2ec701 questions S-W 2025-05-30 19:18:33 +01:00
kschappell e58f5f64ed questions M-R 2025-05-25 12:43:25 +01:00
kschappell 403f3ccbea questions F-L 2025-05-25 11:47:04 +01:00
kschappell c1853224f2 questions D-E 2025-05-25 11:08:40 +01:00
kschappell 16026799f5 questions C 2025-05-25 10:16:13 +01:00
kschappell fff2b6f97b questions B (backspace - burst-balloons) 2025-05-24 22:06:49 +01:00
kschappell 45c100993a questions A (01-matrix - avoid-flood) 2025-05-24 21:40:39 +01:00
kschappell 982793cb8e feat(content): test cases batch 2 2025-05-24 21:00:16 +01:00
kschappell d0c26b204f feat(content): test cases batch 1 2025-05-24 20:55:37 +01:00
kschappell ca585091f3 feat(frontend): integrate editor into question page 2025-05-24 20:06:43 +01:00
kschappell d62f8799a4 submission api client 2025-05-24 19:33:40 +01:00
kschappell a83680cbc5 feat(frontend): add code editor 2025-05-21 21:48:52 +01:00
kschappell 17fa0d84ef pyodide hook for python execution 2025-05-21 21:03:28 +01:00
kschappell e5b87f2332 feat(backend): data loader handles test cases 2025-05-21 20:41:04 +01:00
kschappell 7abf819492 feat(backend): test cases migration 2025-05-21 19:49:22 +01:00
kschappell 3f86c63640 difficulty filters and clickable badges 2025-05-21 18:52:44 +01:00
kschappell 280fed27d4 badge colour variants 2025-05-07 23:23:35 +01:00
kschappell 0a27e6d7af feat(frontend): visual improvements for content 2025-05-07 23:01:33 +01:00
kschappell 21cc48674e security and accessibility pass 2025-05-07 22:15:29 +01:00
kschappell 9a59f6b6b6 ci pipeline and prod compose 2025-04-29 21:36:54 +01:00
kschappell 9630cb3eab frontend dockerfile 2025-04-29 21:28:45 +01:00
kschappell 907ea034a4 wire up pages 2025-04-29 21:06:13 +01:00
kschappell 32d539e735 feat(frontend): add UI components 2025-04-29 20:51:49 +01:00
kschappell 335db61306 types and api client 2025-04-29 19:57:31 +01:00
kschappell 012ec62532 frontend scaffold 2025-04-28 23:50:56 +01:00
kschappell c484a8d302 hard questions 2025-04-28 23:19:49 +01:00
kschappell 352eba7d9d medium tree, graph, dp questions 2025-04-28 23:04:27 +01:00
kschappell 5df1abfb33 medium array/string questions 2025-04-28 22:51:54 +01:00
kschappell 9bd045acc2 easy questions 2025-04-28 21:47:07 +01:00
kschappell 2262f14559 chore(backend): add aiosqlite dep 2025-04-28 20:54:39 +01:00
kschappell 71a60bc70b test(backend): add unit tests 2025-04-26 17:55:18 +01:00
kschappell 8caa9661bd feat(backend): add data loading scripts 2025-04-26 17:39:10 +01:00
kschappell 6aed31f754 init files and module stubs 2025-04-21 22:51:36 +01:00
kschappell cb17533d0b backend dockerfile 2025-04-21 22:17:16 +01:00
kschappell b45aae7b3b feat(content): initial question set 2025-04-21 21:17:13 +01:00
kschappell b8f6d0eb1c feat(content): add categories and patterns 2025-04-21 20:44:21 +01:00
kschappell 4483c60ba6 feat(backend): add API routes 2025-04-20 17:52:49 +01:00
kschappell 43f9384721 feat(backend): add service layer 2025-04-20 16:56:56 +01:00
kschappell b1ba5b1346 feat(backend): add Pydantic schemas 2025-04-15 22:57:23 +01:00
kschappell f49d096c96 feat(backend): add Alembic migrations 2025-04-15 22:47:23 +01:00
kschappell fb4d4078f5 feat(backend): db session config 2025-04-15 21:54:14 +01:00
kschappell 0033b9e5b2 feat(backend): add database models 2025-04-11 00:02:04 +01:00
kschappell 7ee9cbc14c feat(backend): add FastAPI app entry point 2025-04-10 23:30:50 +01:00
kschappell cd76315bef backend config 2025-04-10 23:04:51 +01:00
kschappell 302bf3af47 docker compose for dev 2025-04-10 22:48:45 +01:00
kschappell 41e2f7ba41 initial project setup 2025-04-10 22:02:21 +01:00
4 changed files with 6 additions and 141 deletions
+5 -3
View File
@@ -35,8 +35,6 @@ jobs:
run: mypy src/ run: mypy src/
- name: Test - name: Test
env:
DATABASE_URL: "sqlite+aiosqlite:///:memory:"
run: pytest --cov=src --cov-report=term-missing --cov-fail-under=80 run: pytest --cov=src --cov-report=term-missing --cov-fail-under=80
frontend: frontend:
@@ -63,7 +61,7 @@ jobs:
run: npm run lint run: npm run lint
- name: Type check - name: Type check
run: npx tsc --noEmit run: tsc --noEmit
- name: Test - name: Test
run: npm test run: npm test
@@ -88,6 +86,8 @@ jobs:
context: ./backend context: ./backend
push: false push: false
tags: codetutor-backend:test tags: codetutor-backend:test
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build frontend image - name: Build frontend image
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
@@ -95,3 +95,5 @@ jobs:
context: ./frontend context: ./frontend
push: false push: false
tags: codetutor-frontend:test tags: codetutor-frontend:test
cache-from: type=gha
cache-to: type=gha,mode=max
+1
View File
@@ -75,3 +75,4 @@ frontend/tsconfig.tsbuildinfo
# Local development # Local development
notes/ notes/
scripts/ scripts/
CLAUDE.md
-89
View File
@@ -177,92 +177,3 @@ async def test_get_stats(client: AsyncClient, db_session: AsyncSession) -> None:
assert data["by_difficulty"]["easy"] == 1 assert data["by_difficulty"]["easy"] == 1
assert len(data["by_category"]) == 1 assert len(data["by_category"]) == 1
assert len(data["by_pattern"]) == 1 assert len(data["by_pattern"]) == 1
async def test_get_pattern_tutorial(client: AsyncClient, db_session: AsyncSession) -> None:
await create_sample_data(db_session)
response = await client.get("/api/patterns/two-pointers/tutorial")
assert response.status_code == 200
data = response.json()
assert data["name"] == "Two Pointers"
assert data["learning_progression"]["warmup"][0]["title"] == "Two Sum"
assert len(data["learning_progression"]["core"]) == 0
async def test_get_pattern_tutorial_not_found(client: AsyncClient) -> None:
response = await client.get("/api/patterns/nonexistent/tutorial")
assert response.status_code == 404
async def test_submit_passing(client: AsyncClient, db_session: AsyncSession) -> None:
data = await create_sample_data(db_session)
q = data["question"]
q.test_cases = {
"visible": [{"input": {"nums": [2, 7], "target": 9}, "expected": [0, 1]}],
"hidden": [{"input": {"nums": [3, 2, 4], "target": 6}, "expected": [1, 2]}],
}
await db_session.commit()
response = await client.post(
"/api/questions/two-sum/submit",
json={
"code": "def solve(nums, t): pass",
"hidden_outputs": [{"test_id": 1, "output": [1, 2]}],
},
)
assert response.status_code == 200
result = response.json()
assert result["passed"] is True
assert result["total_passed"] == 1
async def test_submit_wrong_output(client: AsyncClient, db_session: AsyncSession) -> None:
data = await create_sample_data(db_session)
data["question"].test_cases = {
"visible": [],
"hidden": [{"input": {"nums": [1, 2], "target": 3}, "expected": [0, 1]}],
}
await db_session.commit()
response = await client.post(
"/api/questions/two-sum/submit",
json={
"code": "def solve(nums, t): pass",
"hidden_outputs": [{"test_id": 0, "output": [9, 9]}],
},
)
assert response.status_code == 200
assert response.json()["passed"] is False
async def test_submit_missing_output(client: AsyncClient, db_session: AsyncSession) -> None:
data = await create_sample_data(db_session)
data["question"].test_cases = {
"visible": [],
"hidden": [{"input": {"nums": [1, 2], "target": 3}, "expected": [0, 1]}],
}
await db_session.commit()
response = await client.post(
"/api/questions/two-sum/submit",
json={"code": "def solve(nums, t): pass", "hidden_outputs": []},
)
assert response.status_code == 200
assert response.json()["hidden_results"][0]["error"] == "No output provided"
async def test_submit_no_test_cases(client: AsyncClient, db_session: AsyncSession) -> None:
await create_sample_data(db_session)
response = await client.post(
"/api/questions/two-sum/submit",
json={"code": "pass", "hidden_outputs": []},
)
assert response.status_code == 400
async def test_submit_not_found(client: AsyncClient) -> None:
response = await client.post(
"/api/questions/nonexistent/submit",
json={"code": "pass", "hidden_outputs": []},
)
assert response.status_code == 404
-49
View File
@@ -215,55 +215,6 @@ class TestPatternService:
assert result is None assert result is None
async def test_get_tutorial(self, db_session: AsyncSession) -> None:
related = Pattern(name="BFS", slug="bfs", description="Breadth-first search")
pattern = Pattern(
name="Two Pointers",
slug="two-pointers",
description="Two pointer technique",
common_mistakes=[
{"title": "Off by one", "description": "Wrong bounds", "fix": "Use <="}
],
variations=[{"name": "Fast/slow", "description": "Different speeds"}],
related_patterns=["bfs"],
visualization_examples=[{"id": "ex1", "title": "Basic", "code": "x=1", "steps": []}],
)
db_session.add_all([related, pattern])
await db_session.flush()
q1 = Question(
title="Easy Q", slug="easy-q", difficulty=Difficulty.EASY, description="Easy."
)
q2 = Question(title="Med Q", slug="med-q", difficulty=Difficulty.MEDIUM, description="Med.")
q3 = Question(
title="Hard Q", slug="hard-q", difficulty=Difficulty.HARD, description="Hard."
)
q1.patterns = [pattern]
q2.patterns = [pattern]
q3.patterns = [pattern]
db_session.add_all([q1, q2, q3])
await db_session.commit()
service = PatternService(db_session)
result = await service.get_pattern_tutorial("two-pointers")
assert result is not None
assert result.name == "Two Pointers"
assert len(result.common_mistakes) == 1
assert len(result.variations) == 1
assert len(result.related_patterns) == 1
assert result.related_patterns[0].slug == "bfs"
assert len(result.learning_progression.warmup) == 1
assert len(result.learning_progression.core) == 1
assert len(result.learning_progression.challenge) == 1
assert len(result.visualization_examples) == 1
async def test_get_tutorial_not_found(self, db_session: AsyncSession) -> None:
service = PatternService(db_session)
result = await service.get_pattern_tutorial("nonexistent")
assert result is None
class TestStatsService: class TestStatsService:
async def test_get_stats(self, db_session: AsyncSession) -> None: async def test_get_stats(self, db_session: AsyncSession) -> None: