fix(ai): lessonUrl builder in one place; authoring follows core canView

- Learner links go through lessonUrl/courseUrl (src/web/urls.ts).
- Generation, the studio course list and file listing require an authoring
  right and core canView, so a global author needs a grant for a restricted
  course. Contract requests 8 (global author in canView) and 9
  (STC_AI_ALLOWED_BASE_URLS in AppConfig).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas Winther
2026-10-30 04:49:48 +01:00
co-authored by Claude Opus 5.5
parent ead7327bb1
commit f3ce2e34bf
7 changed files with 87 additions and 9 deletions
+10
View File
@@ -34,3 +34,13 @@ test("web routes", () => {
expect(aiWeb.routes.map((r) => r.path)).toEqual(["/c/:courseId/assistant", "/admin/ai", "/account/ai", "/studio/generate"]);
expect(aiWeb.routes.find((r) => r.path === "/admin/ai")!.nav).toEqual({ label: "AI", area: "admin" });
});
test("lesson links go through one builder", async () => {
const { lessonUrl, courseUrl } = await import("../src/web/urls");
expect(lessonUrl("sample", "m2-ex-add")).toBe("/c/sample/m2-ex-add");
expect(lessonUrl("S08 - Docker", "l 1/x")).toBe("/c/S08%20-%20Docker/l%201%2Fx");
expect(courseUrl("sample")).toBe("/c/sample");
const src = await Bun.file(new URL("../src/web/AssistantPage.tsx", import.meta.url)).text();
expect(src).toContain("lessonUrl(courseId, c.lessonId)");
expect(src).not.toMatch(/`\/c\//);
});