canvas-design-mcp 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +200 -0
- package/DESIGN.md +288 -0
- package/PROFESSOR-INSTRUCTIONS.txt +131 -0
- package/README.md +250 -0
- package/dist/canvas-api.d.ts +24 -0
- package/dist/canvas-api.d.ts.map +1 -0
- package/dist/canvas-api.js +146 -0
- package/dist/canvas-api.js.map +1 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +22 -0
- package/dist/config.js.map +1 -0
- package/dist/design-engine.d.ts +5 -0
- package/dist/design-engine.d.ts.map +1 -0
- package/dist/design-engine.js +23 -0
- package/dist/design-engine.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +567 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/accessibility.d.ts +7 -0
- package/dist/tools/accessibility.d.ts.map +1 -0
- package/dist/tools/accessibility.js +149 -0
- package/dist/tools/accessibility.js.map +1 -0
- package/dist/tools/contrast.d.ts +2 -0
- package/dist/tools/contrast.d.ts.map +1 -0
- package/dist/tools/contrast.js +7 -0
- package/dist/tools/contrast.js.map +1 -0
- package/dist/tools/critique.d.ts +23 -0
- package/dist/tools/critique.d.ts.map +1 -0
- package/dist/tools/critique.js +194 -0
- package/dist/tools/critique.js.map +1 -0
- package/dist/tools/generate.d.ts +18 -0
- package/dist/tools/generate.d.ts.map +1 -0
- package/dist/tools/generate.js +101 -0
- package/dist/tools/generate.js.map +1 -0
- package/dist/tools/gotchas.d.ts +7 -0
- package/dist/tools/gotchas.d.ts.map +1 -0
- package/dist/tools/gotchas.js +61 -0
- package/dist/tools/gotchas.js.map +1 -0
- package/dist/tools/ingest.d.ts +44 -0
- package/dist/tools/ingest.d.ts.map +1 -0
- package/dist/tools/ingest.js +211 -0
- package/dist/tools/ingest.js.map +1 -0
- package/dist/tools/list-courses.d.ts +16 -0
- package/dist/tools/list-courses.d.ts.map +1 -0
- package/dist/tools/list-courses.js +79 -0
- package/dist/tools/list-courses.js.map +1 -0
- package/dist/tools/page-io.d.ts +19 -0
- package/dist/tools/page-io.d.ts.map +1 -0
- package/dist/tools/page-io.js +77 -0
- package/dist/tools/page-io.js.map +1 -0
- package/dist/tools/panopto.d.ts +36 -0
- package/dist/tools/panopto.d.ts.map +1 -0
- package/dist/tools/panopto.js +188 -0
- package/dist/tools/panopto.js.map +1 -0
- package/dist/tools/personas.d.ts +21 -0
- package/dist/tools/personas.d.ts.map +1 -0
- package/dist/tools/personas.js +464 -0
- package/dist/tools/personas.js.map +1 -0
- package/dist/tools/philosophy.d.ts +21 -0
- package/dist/tools/philosophy.d.ts.map +1 -0
- package/dist/tools/philosophy.js +137 -0
- package/dist/tools/philosophy.js.map +1 -0
- package/dist/tools/publish.d.ts +31 -0
- package/dist/tools/publish.d.ts.map +1 -0
- package/dist/tools/publish.js +198 -0
- package/dist/tools/publish.js.map +1 -0
- package/dist/tools/redesign.d.ts +18 -0
- package/dist/tools/redesign.d.ts.map +1 -0
- package/dist/tools/redesign.js +68 -0
- package/dist/tools/redesign.js.map +1 -0
- package/dist/tools/update-kb.d.ts +10 -0
- package/dist/tools/update-kb.d.ts.map +1 -0
- package/dist/tools/update-kb.js +93 -0
- package/dist/tools/update-kb.js.map +1 -0
- package/dist/tools/validate.d.ts +10 -0
- package/dist/tools/validate.d.ts.map +1 -0
- package/dist/tools/validate.js +76 -0
- package/dist/tools/validate.js.map +1 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/wizard.d.ts +3 -0
- package/dist/wizard.d.ts.map +1 -0
- package/dist/wizard.js +229 -0
- package/dist/wizard.js.map +1 -0
- package/docs/canvas-design-kb/00-meta/Changelog.md +42 -0
- package/docs/canvas-design-kb/00-meta/Contributing.md +58 -0
- package/docs/canvas-design-kb/00-meta/KB-Overview.md +51 -0
- package/docs/canvas-design-kb/01-canvas-rce/CSS-Inline-Strategy.md +166 -0
- package/docs/canvas-design-kb/01-canvas-rce/Canvas-Built-In-CSS-Classes.md +243 -0
- package/docs/canvas-design-kb/01-canvas-rce/Canvas-Page-Types.md +59 -0
- package/docs/canvas-design-kb/01-canvas-rce/HTML-Allowlist.md +204 -0
- package/docs/canvas-design-kb/01-canvas-rce/RCE-Limitations-and-Workarounds.md +151 -0
- package/docs/canvas-design-kb/01-canvas-rce/RCE-Overview.md +92 -0
- package/docs/canvas-design-kb/02-design-md/DESIGN-MD-Canvas-Template.md +323 -0
- package/docs/canvas-design-kb/02-design-md/DESIGN-MD-File-Structure.md +245 -0
- package/docs/canvas-design-kb/02-design-md/DESIGN-MD-Overview.md +120 -0
- package/docs/canvas-design-kb/02-design-md/DESIGN-MD-Toolchain.md +234 -0
- package/docs/canvas-design-kb/03-design-systems/Color-and-Typography.md +146 -0
- package/docs/canvas-design-kb/03-design-systems/Component-Library.md +299 -0
- package/docs/canvas-design-kb/03-design-systems/Design-System-Principles.md +99 -0
- package/docs/canvas-design-kb/04-tools/Canvas-Theme-Editor.md +40 -0
- package/docs/canvas-design-kb/04-tools/Other-Canvas-Design-Tools.md +47 -0
- package/docs/canvas-design-kb/05-patterns/Course-Home-Page.md +224 -0
- package/docs/canvas-design-kb/06-accessibility/Accessibility-Overview.md +128 -0
- package/docs/canvas-design-kb/07-resources/Inspiration-and-Showcases.md +121 -0
- package/docs/canvas-design-kb/07-resources/Official-Canvas-Links.md +54 -0
- package/docs/canvas-design-kb/README.md +58 -0
- package/docs/feature-roadmap.md +123 -0
- package/docs/installation.md +340 -0
- package/package.json +45 -0
- package/scripts/deploy-public.ps1 +68 -0
- package/src/kb/design-principles.md +45 -0
- package/src/templates/ignite-assignment-page.html +203 -0
- package/src/templates/two-column-dashboard.html +33 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { wcagContrastRatio } from './contrast.js';
|
|
2
|
+
const VAGUE_LINK_TEXT = new Set([
|
|
3
|
+
'click here', 'here', 'read more', 'more', 'link', 'this link', 'learn more',
|
|
4
|
+
]);
|
|
5
|
+
const DECORATIVE_SRC = /spacer|pixel|blank|transparent|1x1/i;
|
|
6
|
+
function ctx(s) {
|
|
7
|
+
return s.length > 60 ? s.slice(0, 60) + '...' : s;
|
|
8
|
+
}
|
|
9
|
+
function checkContrast(html) {
|
|
10
|
+
const warnings = [];
|
|
11
|
+
const styleAttr = /style="([^"]*)"/gi;
|
|
12
|
+
let m;
|
|
13
|
+
while ((m = styleAttr.exec(html)) !== null) {
|
|
14
|
+
const style = m[1];
|
|
15
|
+
// Match background-color:#hex and background:#hex shorthand (not gradients/URLs)
|
|
16
|
+
const bgM = /(?:background-color|background):\s*(#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3})\b/i.exec(style);
|
|
17
|
+
const fgM = /(?<![a-z-])color:\s*(#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3})\b/i.exec(style);
|
|
18
|
+
if (!bgM || !fgM)
|
|
19
|
+
continue;
|
|
20
|
+
let ratio;
|
|
21
|
+
try {
|
|
22
|
+
ratio = wcagContrastRatio(fgM[1], bgM[1]);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const sizeM = /font-size:\s*(\d+(?:\.\d+)?)px/i.exec(style);
|
|
28
|
+
const boldM = /font-weight:\s*(700|bold)\b/i.exec(style);
|
|
29
|
+
const size = sizeM ? parseFloat(sizeM[1]) : 0;
|
|
30
|
+
const isLarge = size >= 24 || (!!boldM && size >= 18);
|
|
31
|
+
const threshold = isLarge ? 3.0 : 4.5;
|
|
32
|
+
if (ratio < threshold) {
|
|
33
|
+
const label = isLarge ? 'large text' : 'body text';
|
|
34
|
+
warnings.push({
|
|
35
|
+
check: 'contrast-ratio',
|
|
36
|
+
message: `${fgM[1]} on ${bgM[1]}: ${ratio.toFixed(2)}:1 — fails WCAG AA for ${label} (requires ${threshold}:1)`,
|
|
37
|
+
context: ctx(style),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return warnings;
|
|
42
|
+
}
|
|
43
|
+
function checkMeaningfulAlt(html) {
|
|
44
|
+
const warnings = [];
|
|
45
|
+
const imgTag = /<img[^>]*>/gi;
|
|
46
|
+
let m;
|
|
47
|
+
while ((m = imgTag.exec(html)) !== null) {
|
|
48
|
+
const img = m[0];
|
|
49
|
+
const altM = /\balt=(["'])(.*?)\1/i.exec(img);
|
|
50
|
+
const srcM = /\bsrc=(["'])(.*?)\1/i.exec(img);
|
|
51
|
+
if (!altM || !srcM)
|
|
52
|
+
continue;
|
|
53
|
+
if (altM[2] === '' && srcM[2] && !DECORATIVE_SRC.test(srcM[2])) {
|
|
54
|
+
warnings.push({
|
|
55
|
+
check: 'empty-alt',
|
|
56
|
+
message: 'Content image has alt="" — add descriptive alt text or confirm it is decorative',
|
|
57
|
+
context: ctx(img),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return warnings;
|
|
62
|
+
}
|
|
63
|
+
function checkHeadingHierarchy(html) {
|
|
64
|
+
const warnings = [];
|
|
65
|
+
const headingTag = /<(h[2-6])[\s>]/gi;
|
|
66
|
+
const seq = [];
|
|
67
|
+
let m;
|
|
68
|
+
while ((m = headingTag.exec(html)) !== null) {
|
|
69
|
+
seq.push({ level: parseInt(m[1][1], 10), tag: m[0] });
|
|
70
|
+
}
|
|
71
|
+
for (let i = 1; i < seq.length; i++) {
|
|
72
|
+
const prev = seq[i - 1].level;
|
|
73
|
+
const curr = seq[i].level;
|
|
74
|
+
if (curr > prev + 1) {
|
|
75
|
+
warnings.push({
|
|
76
|
+
check: 'heading-skip',
|
|
77
|
+
message: `Heading jumps from H${prev} to H${curr} — skipped levels break screen reader navigation`,
|
|
78
|
+
context: ctx(seq[i].tag),
|
|
79
|
+
});
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return warnings;
|
|
84
|
+
}
|
|
85
|
+
function checkDescriptiveLinks(html) {
|
|
86
|
+
const warnings = [];
|
|
87
|
+
const linkTag = /<a[\s][^>]*>([\s\S]*?)<\/a>/gi;
|
|
88
|
+
let m;
|
|
89
|
+
while ((m = linkTag.exec(html)) !== null) {
|
|
90
|
+
const text = m[1].replace(/<[^>]+>/g, '').trim().toLowerCase();
|
|
91
|
+
if (VAGUE_LINK_TEXT.has(text)) {
|
|
92
|
+
warnings.push({
|
|
93
|
+
check: 'vague-link',
|
|
94
|
+
message: `"${text}" is not descriptive — use text that explains where the link goes`,
|
|
95
|
+
context: ctx(m[0]),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return warnings;
|
|
100
|
+
}
|
|
101
|
+
function checkTableHeaders(html) {
|
|
102
|
+
const warnings = [];
|
|
103
|
+
const tableTag = /<table[\s\S]*?<\/table>/gi;
|
|
104
|
+
let m;
|
|
105
|
+
while ((m = tableTag.exec(html)) !== null) {
|
|
106
|
+
if (!/<th[\s>]/i.test(m[0])) {
|
|
107
|
+
warnings.push({
|
|
108
|
+
check: 'table-no-headers',
|
|
109
|
+
message: 'Table has no <th> elements — add headers so screen readers can identify columns and rows',
|
|
110
|
+
context: ctx(m[0]),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return warnings;
|
|
115
|
+
}
|
|
116
|
+
function checkPanoptoNoCaptions(html) {
|
|
117
|
+
const warnings = [];
|
|
118
|
+
const iframeRe = /<iframe[^>]+>/gi;
|
|
119
|
+
let m;
|
|
120
|
+
while ((m = iframeRe.exec(html)) !== null) {
|
|
121
|
+
const tag = m[0];
|
|
122
|
+
const srcM = /\bsrc=(["'])(.*?)\1/i.exec(tag);
|
|
123
|
+
if (!srcM)
|
|
124
|
+
continue;
|
|
125
|
+
const src = srcM[2];
|
|
126
|
+
if (!/panopto/i.test(src))
|
|
127
|
+
continue;
|
|
128
|
+
if (/captions=true/i.test(src))
|
|
129
|
+
continue;
|
|
130
|
+
warnings.push({
|
|
131
|
+
check: 'video-no-captions',
|
|
132
|
+
message: 'Panopto embed found without captions enabled — add &captions=true to the embed URL.',
|
|
133
|
+
context: ctx(tag),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return warnings;
|
|
137
|
+
}
|
|
138
|
+
export function auditAccessibility(html) {
|
|
139
|
+
const stripped = html.replace(/<!--[\s\S]*?-->/g, '');
|
|
140
|
+
return [
|
|
141
|
+
...checkContrast(stripped),
|
|
142
|
+
...checkMeaningfulAlt(stripped),
|
|
143
|
+
...checkHeadingHierarchy(stripped),
|
|
144
|
+
...checkDescriptiveLinks(stripped),
|
|
145
|
+
...checkTableHeaders(stripped),
|
|
146
|
+
...checkPanoptoNoCaptions(stripped),
|
|
147
|
+
];
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=accessibility.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accessibility.js","sourceRoot":"","sources":["../../src/tools/accessibility.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contrast.d.ts","sourceRoot":"","sources":["../../src/tools/contrast.ts"],"names":[],"mappings":"AAEA,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAIpE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contrast.js","sourceRoot":"","sources":["../../src/tools/contrast.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,IAAY;IAC1D,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC;IACpC,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC;IACpC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface CritiqueInput {
|
|
2
|
+
html: string;
|
|
3
|
+
pageType: 'assignment' | 'week-overview' | 'course-home' | 'syllabus' | 'other';
|
|
4
|
+
primaryGoal: string;
|
|
5
|
+
audience?: string;
|
|
6
|
+
mode?: 'quick' | 'comprehensive';
|
|
7
|
+
}
|
|
8
|
+
export interface CritiqueFinding {
|
|
9
|
+
area: 'hierarchy' | 'content' | 'color' | 'typography' | 'layout' | 'completeness';
|
|
10
|
+
issue: string;
|
|
11
|
+
suggestion: string;
|
|
12
|
+
priority: 'high' | 'medium' | 'low';
|
|
13
|
+
}
|
|
14
|
+
export interface CritiqueResult {
|
|
15
|
+
score: number;
|
|
16
|
+
mode: 'quick' | 'comprehensive';
|
|
17
|
+
pageType: string;
|
|
18
|
+
strengths: string[];
|
|
19
|
+
findings: CritiqueFinding[];
|
|
20
|
+
kbContext?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function critiqueCanvasPage(input: CritiqueInput): CritiqueResult;
|
|
23
|
+
//# sourceMappingURL=critique.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"critique.d.ts","sourceRoot":"","sources":["../../src/tools/critique.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,YAAY,GAAG,eAAe,GAAG,aAAa,GAAG,UAAU,GAAG,OAAO,CAAC;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,GAAG,eAAe,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,YAAY,GAAG,QAAQ,GAAG,cAAc,CAAC;IACnF,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;CACrC;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,GAAG,eAAe,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAgLD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CAyBvE"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
const DEDUCTIONS = { high: 15, medium: 8, low: 3 };
|
|
5
|
+
function wordCount(text) {
|
|
6
|
+
return text.split(/\s+/).filter(Boolean).length;
|
|
7
|
+
}
|
|
8
|
+
function stripTags(html) {
|
|
9
|
+
return html.replace(/<[^>]+>/g, '');
|
|
10
|
+
}
|
|
11
|
+
function checkUnreplacedHero(html) {
|
|
12
|
+
if (!html.includes('HERO_IMAGE_URL'))
|
|
13
|
+
return undefined;
|
|
14
|
+
return {
|
|
15
|
+
area: 'completeness',
|
|
16
|
+
issue: 'Hero image placeholder has not been replaced.',
|
|
17
|
+
suggestion: 'Replace HERO_IMAGE_URL with the URL of your hosted 1200×400px banner image.',
|
|
18
|
+
priority: 'high',
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function checkWallOfText(html) {
|
|
22
|
+
const pTag = /<p[^>]*>([\s\S]*?)<\/p>/gi;
|
|
23
|
+
let m;
|
|
24
|
+
while ((m = pTag.exec(html)) !== null) {
|
|
25
|
+
if (wordCount(stripTags(m[1])) > 80) {
|
|
26
|
+
return {
|
|
27
|
+
area: 'content',
|
|
28
|
+
issue: 'A paragraph exceeds 80 words — hard for students to scan quickly.',
|
|
29
|
+
suggestion: 'Break long paragraphs into bullet points or split across multiple section cards.',
|
|
30
|
+
priority: 'high',
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
function checkNoHeadings(html) {
|
|
37
|
+
if (/<h[23][\s>]/i.test(html))
|
|
38
|
+
return undefined;
|
|
39
|
+
return {
|
|
40
|
+
area: 'hierarchy',
|
|
41
|
+
issue: 'Page has no H2 or H3 headings — content has no visible structure.',
|
|
42
|
+
suggestion: 'Add H2 headings to divide major sections. Use H3 for subsections within a card.',
|
|
43
|
+
priority: 'high',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function checkTooSparse(html) {
|
|
47
|
+
const total = wordCount(stripTags(html));
|
|
48
|
+
if (total >= 100)
|
|
49
|
+
return undefined;
|
|
50
|
+
return {
|
|
51
|
+
area: 'content',
|
|
52
|
+
issue: `Page contains only ${total} words — looks unfinished.`,
|
|
53
|
+
suggestion: 'Add more content: an overview, details, submission instructions, or grading notes.',
|
|
54
|
+
priority: 'medium',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function expandHex3(hex) {
|
|
58
|
+
if (hex.length === 4) {
|
|
59
|
+
return '#' + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];
|
|
60
|
+
}
|
|
61
|
+
return hex.toLowerCase();
|
|
62
|
+
}
|
|
63
|
+
function checkColorChaos(html) {
|
|
64
|
+
const hexPattern = /#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}\b/g;
|
|
65
|
+
const colors = new Set((html.match(hexPattern) ?? []).map(expandHex3));
|
|
66
|
+
if (colors.size <= 7)
|
|
67
|
+
return undefined;
|
|
68
|
+
return {
|
|
69
|
+
area: 'color',
|
|
70
|
+
issue: `${colors.size} distinct colors used — visual palette is fragmented.`,
|
|
71
|
+
suggestion: 'Limit to 6–7 colors: primary, secondary, neutrals, and semantic status colors.',
|
|
72
|
+
priority: 'medium',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function checkFontFloor(html) {
|
|
76
|
+
const pattern = /font-size:\s*(\d+(?:\.\d+)?)px/gi;
|
|
77
|
+
let m;
|
|
78
|
+
while ((m = pattern.exec(html)) !== null) {
|
|
79
|
+
if (parseFloat(m[1]) < 13) {
|
|
80
|
+
return {
|
|
81
|
+
area: 'typography',
|
|
82
|
+
issue: `Font size ${m[1]}px found — below the 13px minimum for mobile readability.`,
|
|
83
|
+
suggestion: 'Use a minimum of 13px for all visible text.',
|
|
84
|
+
priority: 'medium',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
function checkMissingSubmissionLanguage(html, pageType) {
|
|
91
|
+
if (pageType !== 'assignment')
|
|
92
|
+
return undefined;
|
|
93
|
+
if (/submit|upload|due|deadline/i.test(html))
|
|
94
|
+
return undefined;
|
|
95
|
+
return {
|
|
96
|
+
area: 'completeness',
|
|
97
|
+
issue: 'Assignment page has no submission instructions — students will not know what to do.',
|
|
98
|
+
suggestion: 'Add a section explaining how to submit, the expected format, and the due date.',
|
|
99
|
+
priority: 'medium',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function extractDivText(html, className) {
|
|
103
|
+
const escapedClass = className.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
104
|
+
const openTagRe = new RegExp(`<div[^>]*class="[^"]*${escapedClass}[^"]*"[^>]*>`);
|
|
105
|
+
const openTagMatch = openTagRe.exec(html);
|
|
106
|
+
if (!openTagMatch)
|
|
107
|
+
return '';
|
|
108
|
+
const start = openTagMatch.index + openTagMatch[0].length;
|
|
109
|
+
let depth = 1;
|
|
110
|
+
let pos = start;
|
|
111
|
+
while (pos < html.length && depth > 0) {
|
|
112
|
+
const openIdx = html.indexOf('<div', pos);
|
|
113
|
+
const closeIdx = html.indexOf('</div>', pos);
|
|
114
|
+
if (closeIdx < 0)
|
|
115
|
+
break;
|
|
116
|
+
if (openIdx >= 0 && openIdx < closeIdx) {
|
|
117
|
+
const ch = html[openIdx + 4];
|
|
118
|
+
if (ch === '>' || ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r')
|
|
119
|
+
depth++;
|
|
120
|
+
pos = openIdx + 4;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
depth--;
|
|
124
|
+
if (depth === 0)
|
|
125
|
+
return stripTags(html.slice(start, closeIdx));
|
|
126
|
+
pos = closeIdx + 6;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return stripTags(html.slice(start, pos));
|
|
130
|
+
}
|
|
131
|
+
function checkColumnImbalance(html) {
|
|
132
|
+
if (!html.includes('col-md-8') || !html.includes('col-md-4'))
|
|
133
|
+
return undefined;
|
|
134
|
+
const wideWords = wordCount(extractDivText(html, 'col-md-8'));
|
|
135
|
+
const narrowWords = wordCount(extractDivText(html, 'col-md-4'));
|
|
136
|
+
if (narrowWords === 0 || wideWords / narrowWords <= 3)
|
|
137
|
+
return undefined;
|
|
138
|
+
return {
|
|
139
|
+
area: 'layout',
|
|
140
|
+
issue: 'Left column has significantly more content than the sidebar — layout feels lopsided.',
|
|
141
|
+
suggestion: 'Move secondary content (grading notes, resources) into the sidebar to balance columns.',
|
|
142
|
+
priority: 'low',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function calculateScore(findings) {
|
|
146
|
+
const deduction = findings.reduce((sum, f) => sum + DEDUCTIONS[f.priority], 0);
|
|
147
|
+
return Math.max(0, 100 - deduction);
|
|
148
|
+
}
|
|
149
|
+
function deriveStrengths(html, findings) {
|
|
150
|
+
const foundAreas = new Set(findings.map(f => f.area));
|
|
151
|
+
const strengths = [];
|
|
152
|
+
if (!foundAreas.has('hierarchy') && /<h[23][\s>]/i.test(html)) {
|
|
153
|
+
strengths.push('Clear heading structure');
|
|
154
|
+
}
|
|
155
|
+
if (!foundAreas.has('color')) {
|
|
156
|
+
strengths.push('Consistent color palette');
|
|
157
|
+
}
|
|
158
|
+
if (!foundAreas.has('content')) {
|
|
159
|
+
strengths.push('Well-proportioned content length');
|
|
160
|
+
}
|
|
161
|
+
return strengths.slice(0, 3);
|
|
162
|
+
}
|
|
163
|
+
function loadKb() {
|
|
164
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
165
|
+
try {
|
|
166
|
+
return readFileSync(join(__dirname, '../../src/kb/design-principles.md'), 'utf-8');
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return '';
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
export function critiqueCanvasPage(input) {
|
|
173
|
+
const { html, pageType, mode = 'quick' } = input;
|
|
174
|
+
const findings = [
|
|
175
|
+
checkUnreplacedHero(html),
|
|
176
|
+
checkWallOfText(html),
|
|
177
|
+
checkNoHeadings(html),
|
|
178
|
+
checkTooSparse(html),
|
|
179
|
+
checkColorChaos(html),
|
|
180
|
+
checkFontFloor(html),
|
|
181
|
+
checkMissingSubmissionLanguage(html, pageType),
|
|
182
|
+
checkColumnImbalance(html),
|
|
183
|
+
].filter((f) => f !== undefined);
|
|
184
|
+
const score = calculateScore(findings);
|
|
185
|
+
const strengths = deriveStrengths(html, findings);
|
|
186
|
+
const result = { score, mode, pageType, strengths, findings };
|
|
187
|
+
if (mode === 'comprehensive') {
|
|
188
|
+
const kb = loadKb();
|
|
189
|
+
if (kb)
|
|
190
|
+
result.kbContext = kb;
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=critique.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"critique.js","sourceRoot":"","sources":["../../src/tools/critique.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { InstitutionConfig } from '../types.js';
|
|
2
|
+
export interface GenerateInput {
|
|
3
|
+
assignmentBrief: string;
|
|
4
|
+
courseName: string;
|
|
5
|
+
courseNumber: string;
|
|
6
|
+
assignmentNumber: string;
|
|
7
|
+
professorName: string;
|
|
8
|
+
semester: string;
|
|
9
|
+
styleNotes?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface GenerateOutput {
|
|
12
|
+
html: string;
|
|
13
|
+
heroImagePrompt: string;
|
|
14
|
+
filename: string;
|
|
15
|
+
warnings: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare function generateCanvasPage(input: GenerateInput, config: InstitutionConfig): GenerateOutput;
|
|
18
|
+
//# sourceMappingURL=generate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/tools/generate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,MAAM,WAAW,aAAa;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAgCD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,GAAG,cAAc,CAkElG"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { resolveTokens } from '../design-engine.js';
|
|
2
|
+
import { validateCanvasHtml } from './validate.js';
|
|
3
|
+
import { auditAccessibility } from './accessibility.js';
|
|
4
|
+
function buildStatBadge(value, label) {
|
|
5
|
+
return (`<div style="background:rgba(255,255,255,0.15);border-radius:8px;padding:8px 16px;` +
|
|
6
|
+
`margin-right:10px;margin-bottom:8px;text-align:center;color:#ffffff;">` +
|
|
7
|
+
`<div style="font-size:22px;font-weight:700;font-family:Lato,sans-serif;">${value}</div>` +
|
|
8
|
+
`<div style="font-size:11px;color:rgba(255,255,255,0.85);font-family:Lato,sans-serif;">${label}</div>` +
|
|
9
|
+
`</div>`);
|
|
10
|
+
}
|
|
11
|
+
function buildSectionCard(label, content, accentColor) {
|
|
12
|
+
return (`<div style="background:#ffffff;border-radius:10px;border:1px solid #e0e0d8;padding:20px 24px;margin-bottom:14px;">` +
|
|
13
|
+
`<div style="font-size:11px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;` +
|
|
14
|
+
`color:${accentColor};margin-bottom:8px;font-family:Lato,sans-serif;">${label}</div>` +
|
|
15
|
+
content +
|
|
16
|
+
`</div>`);
|
|
17
|
+
}
|
|
18
|
+
function buildSidebarCard(title, content, bgColor, textColor) {
|
|
19
|
+
return (`<div style="background:${bgColor};border-radius:10px;padding:16px 20px;margin-bottom:12px;border:1px solid #e0e0d8;">` +
|
|
20
|
+
`<div style="font-size:11px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;` +
|
|
21
|
+
`color:${textColor};margin-bottom:8px;font-family:Lato,sans-serif;">${title}</div>` +
|
|
22
|
+
content +
|
|
23
|
+
`</div>`);
|
|
24
|
+
}
|
|
25
|
+
export function generateCanvasPage(input, config) {
|
|
26
|
+
const { courseNumber, assignmentNumber, courseName, assignmentBrief, professorName, semester } = input;
|
|
27
|
+
const lines = assignmentBrief.split('\n').filter(l => l.trim());
|
|
28
|
+
const overview = lines.slice(0, 3).join(' ').trim();
|
|
29
|
+
const details = lines.slice(3);
|
|
30
|
+
const overviewContent = `<p style="font-size:15px;color:#1A1A1A;line-height:1.65;margin:0;font-family:Lato,sans-serif;">${overview}</p>`;
|
|
31
|
+
const detailItems = details
|
|
32
|
+
.map(line => `<div style="display:flex;align-items:flex-start;margin-bottom:10px;">` +
|
|
33
|
+
`<span style="color:${config.colors.secondary};font-weight:700;font-size:16px;margin-right:10px;line-height:1.4;">→</span>` +
|
|
34
|
+
`<p style="font-size:14px;color:#1A1A1A;margin:0;line-height:1.65;font-family:Lato,sans-serif;">${line.replace(/^[-*]\s*/, '')}</p>` +
|
|
35
|
+
`</div>`)
|
|
36
|
+
.join('\n');
|
|
37
|
+
const leftContent = buildSectionCard('Overview', overviewContent, config.colors.primary) +
|
|
38
|
+
(detailItems ? buildSectionCard('Details', detailItems, config.colors.primary) : '');
|
|
39
|
+
const gradingContent = `<div style="border-left:3px solid ${config.colors.secondary};padding-left:12px;">` +
|
|
40
|
+
`<p style="font-size:14px;color:#1A1A1A;line-height:1.65;margin:0;font-family:Lato,sans-serif;">See rubric in Canvas for grading criteria.</p>` +
|
|
41
|
+
`</div>`;
|
|
42
|
+
const rightContent = `<div style="background:${config.colors.primary};border-radius:10px;padding:20px;margin-bottom:12px;">` +
|
|
43
|
+
`<div style="font-size:11px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;` +
|
|
44
|
+
`color:rgba(255,255,255,0.7);margin-bottom:12px;font-family:Lato,sans-serif;">What to Submit</div>` +
|
|
45
|
+
`<p style="color:rgba(255,255,255,0.85);font-size:13px;font-family:Lato,sans-serif;">Submit via Canvas. Check the assignment for specific requirements.</p>` +
|
|
46
|
+
`</div>` +
|
|
47
|
+
buildSidebarCard('Grading', gradingContent, '#ffffff', '#555550');
|
|
48
|
+
const statBadges = buildStatBadge(professorName, 'Professor') +
|
|
49
|
+
buildStatBadge(semester, 'Semester');
|
|
50
|
+
const html = buildFullPage({
|
|
51
|
+
courseNumber,
|
|
52
|
+
assignmentNumber,
|
|
53
|
+
courseName,
|
|
54
|
+
subtitle: `${courseNumber} · ${semester}`,
|
|
55
|
+
heroAlt: `${courseName} assignment hero image`,
|
|
56
|
+
statBadges,
|
|
57
|
+
leftContent,
|
|
58
|
+
rightContent,
|
|
59
|
+
}, config);
|
|
60
|
+
const validation = validateCanvasHtml(html);
|
|
61
|
+
const a11y = auditAccessibility(html);
|
|
62
|
+
const warnings = [
|
|
63
|
+
...validation.violations.map(v => v.rule),
|
|
64
|
+
...a11y.map(w => `a11y: ${w.check} — ${w.message}`),
|
|
65
|
+
];
|
|
66
|
+
const heroImagePrompt = `A cinematic wide-format banner image for a university course assignment about ${courseName}. ` +
|
|
67
|
+
`Show a dynamic, professional academic workspace. Color palette dominated by ${config.colors.primary} ` +
|
|
68
|
+
`with accent highlights in ${config.colors.secondary}. ` +
|
|
69
|
+
`Clean, professional, slightly cinematic mood. No text. No people. Horizontal format, 3:1 aspect ratio, high resolution.`;
|
|
70
|
+
const filename = `${courseNumber.toLowerCase().replace(/\s+/g, '-')}-${assignmentNumber}-page.html`;
|
|
71
|
+
return { html, heroImagePrompt, filename, warnings };
|
|
72
|
+
}
|
|
73
|
+
function buildFullPage(tokens, config) {
|
|
74
|
+
const resolved = resolveTokens('{{colors.primaryDark}}|{{colors.primary}}|{{colors.secondary}}', config).split('|');
|
|
75
|
+
const [primaryDark, primary, secondary] = resolved;
|
|
76
|
+
return `<!-- Canvas Design Studio — Generated Page -->
|
|
77
|
+
<!-- Canvas-safe: inline styles only, no scripts, no gap, no box-shadow -->
|
|
78
|
+
<div style="max-width:860px;margin:0 auto;font-family:Lato,sans-serif;background:#F4F3EF;padding:16px;">
|
|
79
|
+
|
|
80
|
+
<!-- HERO BANNER -->
|
|
81
|
+
<div style="background:linear-gradient(135deg,${primaryDark} 0%,${primary} 60%,#1A5BCC 100%);border-radius:14px;overflow:hidden;margin-bottom:20px;">
|
|
82
|
+
<img src="HERO_IMAGE_URL" alt="${tokens.heroAlt}" style="width:100%;height:200px;object-fit:cover;display:block;border-radius:14px 14px 0 0;">
|
|
83
|
+
<div style="padding:28px 32px;">
|
|
84
|
+
<div style="display:inline-block;background:${secondary};color:#ffffff;font-size:11px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;padding:3px 12px;border-radius:20px;margin-bottom:10px;">${tokens.courseNumber} · ${tokens.assignmentNumber}</div>
|
|
85
|
+
<h2 style="color:#ffffff;font-size:28px;font-weight:700;line-height:1.2;margin:0 0 6px 0;font-family:Lato,sans-serif;">${tokens.courseName}</h2>
|
|
86
|
+
<p style="color:rgba(255,255,255,0.85);font-size:15px;font-weight:400;margin:0 0 20px 0;font-family:Lato,sans-serif;">${tokens.subtitle}</p>
|
|
87
|
+
<div style="display:flex;flex-wrap:wrap;">${tokens.statBadges}</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<!-- TWO-COLUMN BODY -->
|
|
92
|
+
<div class="content-box">
|
|
93
|
+
<div class="grid-row">
|
|
94
|
+
<div class="col-xs-12 col-md-8" style="padding-right:12px;">${tokens.leftContent}</div>
|
|
95
|
+
<div class="col-xs-12 col-md-4">${tokens.rightContent}</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
</div>`;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/tools/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAoBxD,SAAS,cAAc,CAAC,KAAa,EAAE,KAAa;IAClD,OAAO,CACL,mFAAmF;QACnF,wEAAwE;QACxE,4EAA4E,KAAK,QAAQ;QACzF,yFAAyF,KAAK,QAAQ;QACtG,QAAQ,CACT,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAa,EAAE,OAAe,EAAE,WAAmB;IAC3E,OAAO,CACL,oHAAoH;QACpH,4FAA4F;QAC5F,SAAS,WAAW,oDAAoD,KAAK,QAAQ;QACrF,OAAO;QACP,QAAQ,CACT,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAa,EAAE,OAAe,EAAE,OAAe,EAAE,SAAiB;IAC1F,OAAO,CACL,0BAA0B,OAAO,sFAAsF;QACvH,4FAA4F;QAC5F,SAAS,SAAS,oDAAoD,KAAK,QAAQ;QACnF,OAAO;QACP,QAAQ,CACT,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAoB,EAAE,MAAyB;IAChF,MAAM,EAAE,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAEvG,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAChE,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACpD,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAE/B,MAAM,eAAe,GAAG,kGAAkG,QAAQ,MAAM,CAAC;IAEzI,MAAM,WAAW,GAAG,OAAO;SACxB,GAAG,CAAC,IAAI,CAAC,EAAE,CACV,uEAAuE;QACvE,sBAAsB,MAAM,CAAC,MAAM,CAAC,SAAS,mFAAmF;QAChI,kGAAkG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,MAAM;QACpI,QAAQ,CACT;SACA,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,WAAW,GACf,gBAAgB,CAAC,UAAU,EAAE,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;QACpE,CAAC,WAAW,CAAC,CAAC,CAAC,gBAAgB,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAEvF,MAAM,cAAc,GAClB,qCAAqC,MAAM,CAAC,MAAM,CAAC,SAAS,uBAAuB;QACnF,+IAA+I;QAC/I,QAAQ,CAAC;IAEX,MAAM,YAAY,GAChB,0BAA0B,MAAM,CAAC,MAAM,CAAC,OAAO,wDAAwD;QACvG,4FAA4F;QAC5F,mGAAmG;QACnG,4JAA4J;QAC5J,QAAQ;QACR,gBAAgB,CAAC,SAAS,EAAE,cAAc,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;IAEpE,MAAM,UAAU,GACd,cAAc,CAAC,aAAa,EAAE,WAAW,CAAC;QAC1C,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAEvC,MAAM,IAAI,GAAG,aAAa,CAAC;QACzB,YAAY;QACZ,gBAAgB;QAChB,UAAU;QACV,QAAQ,EAAE,GAAG,YAAY,aAAa,QAAQ,EAAE;QAChD,OAAO,EAAE,GAAG,UAAU,wBAAwB;QAC9C,UAAU;QACV,WAAW;QACX,YAAY;KACb,EAAE,MAAM,CAAC,CAAC;IAEX,MAAM,UAAU,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG;QACf,GAAG,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACzC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;KACpD,CAAC;IAEF,MAAM,eAAe,GACnB,iFAAiF,UAAU,IAAI;QAC/F,+EAA+E,MAAM,CAAC,MAAM,CAAC,OAAO,GAAG;QACvG,6BAA6B,MAAM,CAAC,MAAM,CAAC,SAAS,IAAI;QACxD,yHAAyH,CAAC;IAE5H,MAAM,QAAQ,GAAG,GAAG,YAAY,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,gBAAgB,YAAY,CAAC;IAEpG,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AACvD,CAAC;AAaD,SAAS,aAAa,CAAC,MAAkB,EAAE,MAAyB;IAClE,MAAM,QAAQ,GAAG,aAAa,CAC5B,gEAAgE,EAChE,MAAM,CACP,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACb,MAAM,CAAC,WAAW,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC;IAEnD,OAAO;;;;;kDAKyC,WAAW,OAAO,OAAO;qCACtC,MAAM,CAAC,OAAO;;oDAEC,SAAS,yJAAyJ,MAAM,CAAC,YAAY,aAAa,MAAM,CAAC,gBAAgB;+HAC9I,MAAM,CAAC,UAAU;8HAClB,MAAM,CAAC,QAAQ;kDAC3F,MAAM,CAAC,UAAU;;;;;;;oEAOC,MAAM,CAAC,WAAW;wCAC9C,MAAM,CAAC,YAAY;;;;OAIpD,CAAC;AACR,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CanvasCourse } from '../types.js';
|
|
2
|
+
export declare function courseCoordinatorGotcha(course: CanvasCourse): string | undefined;
|
|
3
|
+
export declare function titleCollisionGotcha(existingTitle: string, newTitle: string, score: number): string;
|
|
4
|
+
export declare function tokenScopeGotcha(canvasUrl: string): string;
|
|
5
|
+
export declare function ferpaGotcha(reason: string, line: number): string;
|
|
6
|
+
export declare function versionControlTip(): string;
|
|
7
|
+
//# sourceMappingURL=gotchas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gotchas.d.ts","sourceRoot":"","sources":["../../src/tools/gotchas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAwBhD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAahF;AAED,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAanG;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAO1D;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
function plural(count, singular, pluralValue = `${singular}s`) {
|
|
2
|
+
return `${count} ${count === 1 ? singular : pluralValue}`;
|
|
3
|
+
}
|
|
4
|
+
function teacherCount(course) {
|
|
5
|
+
if (course.teachers)
|
|
6
|
+
return course.teachers.length;
|
|
7
|
+
return course.enrollments?.filter(enrollment => enrollment.type === 'teacher' || enrollment.role === 'TeacherEnrollment').length ?? 0;
|
|
8
|
+
}
|
|
9
|
+
function studentCount(course) {
|
|
10
|
+
if (typeof course.total_students === 'number')
|
|
11
|
+
return course.total_students;
|
|
12
|
+
return course.enrollments?.filter(enrollment => enrollment.type === 'student' || enrollment.role === 'StudentEnrollment').length ?? 0;
|
|
13
|
+
}
|
|
14
|
+
function normalizeCanvasUrl(canvasUrl) {
|
|
15
|
+
return canvasUrl.replace(/\/+$/, '');
|
|
16
|
+
}
|
|
17
|
+
export function courseCoordinatorGotcha(course) {
|
|
18
|
+
const teachers = teacherCount(course);
|
|
19
|
+
const students = studentCount(course);
|
|
20
|
+
if (students === 0 || teachers >= 3) {
|
|
21
|
+
return [
|
|
22
|
+
`Heads up: this course has ${plural(teachers, 'teacher')} and ${plural(students, 'student')}.`,
|
|
23
|
+
'If you are listed as a coordinator rather than the instructor of record, publishing here may affect a live course you do not manage.',
|
|
24
|
+
'Double-check that this is the right course before publishing.',
|
|
25
|
+
].join(' ');
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
export function titleCollisionGotcha(existingTitle, newTitle, score) {
|
|
30
|
+
return [
|
|
31
|
+
'A page with a similar title already exists:',
|
|
32
|
+
` Existing: "${existingTitle}"`,
|
|
33
|
+
` New: "${newTitle}"`,
|
|
34
|
+
` Similarity: ${score.toFixed(2)}`,
|
|
35
|
+
'',
|
|
36
|
+
'Rerun publish_to_canvas with one of these options:',
|
|
37
|
+
' collisionAction: "update" to replace the existing page content',
|
|
38
|
+
' collisionAction: "create" to create a new page with this title',
|
|
39
|
+
' collisionAction: "related" and relatedPageTitle to create a clearly named variation',
|
|
40
|
+
' collisionAction: "cancel" to stop without changing Canvas',
|
|
41
|
+
].join('\n');
|
|
42
|
+
}
|
|
43
|
+
export function tokenScopeGotcha(canvasUrl) {
|
|
44
|
+
const settingsUrl = `${normalizeCanvasUrl(canvasUrl)}/profile/settings`;
|
|
45
|
+
return [
|
|
46
|
+
'Your Canvas API token or Canvas role does not allow editing pages in this course.',
|
|
47
|
+
`If you expected this to work, check that you can edit Pages in the course and generate a new token at ${settingsUrl}.`,
|
|
48
|
+
'Then run setup_institution to update the stored token.',
|
|
49
|
+
].join(' ');
|
|
50
|
+
}
|
|
51
|
+
export function ferpaGotcha(reason, line) {
|
|
52
|
+
return [
|
|
53
|
+
`This HTML may contain student data (${reason} near line ${line}).`,
|
|
54
|
+
'Publishing student records to a Canvas page may violate FERPA.',
|
|
55
|
+
'Review before continuing. Pass skipFerpaCheck: true only after confirming the content is safe to publish.',
|
|
56
|
+
].join(' ');
|
|
57
|
+
}
|
|
58
|
+
export function versionControlTip() {
|
|
59
|
+
return 'Tip: Save your HTML source to a Git repo before publishing. Canvas stores page revisions, but they are hard to diff and expire. Git is the right tool for tracking changes over time.';
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=gotchas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gotchas.js","sourceRoot":"","sources":["../../src/tools/gotchas.ts"],"names":[],"mappings":"AAEA,SAAS,MAAM,CAAC,KAAa,EAAE,QAAgB,EAAE,WAAW,GAAG,GAAG,QAAQ,GAAG;IAC3E,OAAO,GAAG,KAAK,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;AAC5D,CAAC;AAED,SAAS,YAAY,CAAC,MAAoB;IACxC,IAAI,MAAM,CAAC,QAAQ;QAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;IACnD,OAAO,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,CAC7C,UAAU,CAAC,IAAI,KAAK,SAAS,IAAI,UAAU,CAAC,IAAI,KAAK,mBAAmB,CACzE,CAAC,MAAM,IAAI,CAAC,CAAC;AAChB,CAAC;AAED,SAAS,YAAY,CAAC,MAAoB;IACxC,IAAI,OAAO,MAAM,CAAC,cAAc,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,cAAc,CAAC;IAC5E,OAAO,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,CAC7C,UAAU,CAAC,IAAI,KAAK,SAAS,IAAI,UAAU,CAAC,IAAI,KAAK,mBAAmB,CACzE,CAAC,MAAM,IAAI,CAAC,CAAC;AAChB,CAAC;AAED,SAAS,kBAAkB,CAAC,SAAiB;IAC3C,OAAO,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,MAAoB;IAC1D,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAEtC,IAAI,QAAQ,KAAK,CAAC,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;QACpC,OAAO;YACL,6BAA6B,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,QAAQ,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG;YAC9F,sIAAsI;YACtI,+DAA+D;SAChE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACd,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,aAAqB,EAAE,QAAgB,EAAE,KAAa;IACzF,OAAO;QACL,6CAA6C;QAC7C,gBAAgB,aAAa,GAAG;QAChC,gBAAgB,QAAQ,GAAG;QAC3B,iBAAiB,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;QACnC,EAAE;QACF,oDAAoD;QACpD,kEAAkE;QAClE,kEAAkE;QAClE,uFAAuF;QACvF,6DAA6D;KAC9D,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,MAAM,WAAW,GAAG,GAAG,kBAAkB,CAAC,SAAS,CAAC,mBAAmB,CAAC;IACxE,OAAO;QACL,mFAAmF;QACnF,yGAAyG,WAAW,GAAG;QACvH,wDAAwD;KACzD,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,IAAY;IACtD,OAAO;QACL,uCAAuC,MAAM,cAAc,IAAI,IAAI;QACnE,gEAAgE;QAChE,2GAA2G;KAC5G,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,uLAAuL,CAAC;AACjM,CAAC"}
|