Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
50c3311
docs(spec): landing page below the hero (#344 phase 2)
AndresL230 Aug 1, 2026
0691218
docs(plan): landing interactive graph, step 1 (#344)
AndresL230 Aug 1, 2026
651de5f
feat(landing): course graph fixtures for the interactive demo (#344)
AndresL230 Aug 1, 2026
f5b89b5
feat(landing): deterministic radial layout + helical entry path (#344)
AndresL230 Aug 1, 2026
d6fc3b9
feat(landing): knowledge graph demo — chips and laid-out render (#344)
AndresL230 Aug 1, 2026
69a718b
feat(landing): helical assembly, parked under reduced motion (#344)
AndresL230 Aug 1, 2026
fed4d12
fix(landing): SSR-safe reduced-motion detection for the graph demo (#…
AndresL230 Aug 1, 2026
fe906f9
test(landing): cold-start the reduced-motion store cache per test (#344)
AndresL230 Aug 1, 2026
1a3c2f5
feat(landing): graph hover blurbs and copy fade on engagement (#344)
AndresL230 Aug 1, 2026
d42d68b
feat(landing): replace HowItWorks and the feature catalog with the gr…
AndresL230 Aug 1, 2026
bb8ca16
test(e2e): journey for the landing knowledge graph (#344)
AndresL230 Aug 1, 2026
1e026e1
fix(landing): viewport-gate the graph assembly, never render it blank…
AndresL230 Aug 1, 2026
5ab1eda
fix(landing): brand-conform the graph section and fit its frame (#344)
AndresL230 Aug 1, 2026
b9e422e
fix(landing): take the outer ring off the horizontal axis (#344)
AndresL230 Aug 1, 2026
5631639
fix(landing): lay the graph out as a radial tree, not concentric ring…
AndresL230 Aug 1, 2026
fc0afe8
fix(landing): grow the graph upward from its base, not down from the …
AndresL230 Aug 2, 2026
5429aa6
fix(landing): tighten the graph's entry sweep to a quarter turn (#344)
AndresL230 Aug 2, 2026
86cfacf
fix(landing): restore atmospheric continuity between the sections (#344)
AndresL230 Aug 2, 2026
c99ed01
feat(landing): three feature bands and a bento of built surfaces (#344)
AndresL230 Aug 2, 2026
7e4ea24
feat(landing): give the knowledge graph product chrome, a dial and a …
AndresL230 Aug 2, 2026
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
62 changes: 62 additions & 0 deletions docs/frontend-testids.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -80,6 +80,10 @@ renders the element.
| Calendar | `calendar` | `frontend/src/components/screens/Calendar.tsx` (the `/calendar` screen — today just the #185 load-failure banner + retry) |
| Gradebook | `gradebook` | `frontend/src/components/screens/Gradebook/Landing.tsx` + `Course.tsx` (the `/gradebook` screens), `frontend/src/components/Gradebook/TranscriptModal.tsx` (the transcript modal), `frontend/src/components/Gradebook/CourseCard.tsx` (the term-aware card links), `frontend/src/components/Gradebook/AssignmentList.tsx` + `AssignmentModal.tsx` (the add-assignment flow the #468 mutation leg drives) — added with the #139 term-switcher/transcript journey |
| Admin analytics | `admin-analytics` | `frontend/src/components/screens/AdminAnalytics.tsx` (the `/admin/analytics` dashboard — range presets/inputs, cost group-by toggle, per-panel retry) — added with the #121 data layer |
| Landing knowledge graph demo | `landing-graph` | `frontend/src/components/marketing/graph/KnowledgeGraphDemo.tsx` (the pre-auth landing page's interactive course-picker + laid-out concept graph, wearing the `landing-surface` chrome with an inspector rail and mastery legend since #344 step 3) |
| Landing feature bands | `landing-band` | `frontend/src/components/marketing/FeatureBand.tsx` (the three full-width bands below the graph; content + side-alternation in `featureBands.tsx`, #344 step 2) |
| Landing surface bento | `landing-bento` | `frontend/src/components/marketing/SurfaceBento.tsx` (the four-tile grid of built product surfaces, #344 step 2) |
| Landing product surfaces | `landing-surface` | `frontend/src/components/marketing/surfaces/*.tsx` (the seven in-page recreations the bands and bento mount, #344 step 2) |

Two surfaces do **not** carry their testids in the screen file named by the
route:
Expand DownExpand Up@@ -324,6 +328,64 @@ Added with the upload → SSE → library journey (#387).
| `library-concepts-toggle-all` | detail panel "Expand all" / "Collapse all" |
| `library-concept-toggle-{idx}` | one concept accordion toggle (render index) |

### `landing-graph`

Added with the #344 landing-page knowledge-graph demo. Ships the static,
fully laid-out render (chips to switch course, nodes/edges for the selected
graph), the helical assembly animation, and hover interaction (detail-panel
swap + copy fade on first engagement); click-to-expand is a later task in the
same spec and will reuse these same testids.

Step 3 wrapped the whole thing in the same `landing-surface` chrome the bands
and bento use — a titled bar over a `--bg-mesh` canvas, an inspector rail and a
mastery legend — so the ids below split into the section's own controls and the
screen's panes.

| testid | element |
| --- | --- |
| `landing-graph` | demo section root (`aria-label` names the selected course) |
| `landing-graph-chip-{courseId}` | one course-picker chip, per `COURSE_GRAPHS` entry — `aria-pressed` marks the active course |
| `landing-graph-copy` | the section's instructional copy block (eyebrow label + heading) — `data-engaged` flips `"true"` on the visitor's first node hover and stays that way across course switches |
| `landing-graph-eyebrow` | the "Your knowledge, mapped" eyebrow label — deliberately *not* faded on engagement (WCAG AA at 0.7rem) |
| `landing-graph-headline` | the "Pick a course. Watch it grow." heading — carries the engagement fade (`ENGAGED_HEADLINE_OPACITY`) |
| `landing-graph-surface` | the product-chrome frame the whole screen sits in (`landing-surface`) |
| `landing-graph-meta` | the chrome bar's right-hand status — `{code} · {conceptCount} concepts · {n}% mastery`, all read off the selected `CourseGraph` |
| `landing-graph-svg` | the `<svg>` itself — its `viewBox` is the desktop or the phone `GraphView` (`graph/layout.ts`), swapped at the mobile breakpoint |
| `landing-graph-node-{nodeId}` | one SVG node group (`<g>`) in the selected course's graph. Its **first `<circle>` is the tier-painted disc at the full node radius** — the phone legibility gate measures that element, so nothing may be drawn in front of it |
| `landing-graph-detail` | the inspector rail's frame. Never empty: with nothing hovered it shows the course (the root node) |
| `landing-graph-detail-name` | the inspected node's name |
| `landing-graph-detail-tier` | its mastery tier as a labelled chip (`Course` for the root, which is an anchor rather than a status) |
| `landing-graph-detail-mastery` | its mastery as a percentage |
| `landing-graph-blurb` | the reserved-height paragraph in the panel that shows the inspected node's one-sentence blurb |
| `landing-graph-legend` | the mastery legend pinned to the foot of the rail |
| `landing-graph-legend-{tier}` | one legend row — `mastered`, `learning`, `struggling` or `unexplored`, each with the tier's own count of drawn concepts |

### `landing-band` / `landing-bento` / `landing-surface`

Added with the #344 step-2 build (feature bands + surface bento). These
surfaces are **static recreations** of shipped product screens — there is
deliberately not a single `<button>`, `<input>` or `<textarea>` among them
(the spec's "no per-tile buttons"), so the lint block below is satisfied
vacuously and stays as a guard on anything added later.

| testid | element |
| --- | --- |
| `landing-band-{bandId}` | one feature band's `<section>` — `bandId` is `upload`, `quiz` or `review` (a stable content id, per the repeated-items rule). Carries `data-surface-side="left"\|"right"`, the resolved alternation |
| `landing-band-{bandId}-eyebrow` | that band's mono micro-label |
| `landing-bento` | the four-tile grid's `<section>` root |
| `landing-bento-eyebrow` | the bento's mono micro-label |
| `landing-surface-upload` | Document Library ingest queue (band 1) |
| `landing-surface-quiz` | an Adaptive Quizzes question mid-answer (band 2) |
| `landing-surface-review` | the Study screen's spaced review + rating trio (band 3) |
| `landing-surface-tutor` | a Tutor exchange (bento) |
| `landing-surface-notes` | a Notetaker note with its linked concepts (bento) |
| `landing-surface-rooms` | a Study Rooms conversation (bento) |
| `landing-surface-gradebook` | a Gradebook course header + assignment rows (bento) |

The `landing-surface-*` ids suffix the *surface* rather than a render index
because each one mounts exactly once and the surface name is the stable
domain id here.

## Enforcement

`frontend/eslint.config.mjs` has a per-file `no-restricted-syntax` block scoped
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
feat(landing): interactive knowledge graph replaces the scroll section (#344 phase 2) by AndresL230 · Pull Request #513 · SaplingLearn/Sapling · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
50c3311
docs(spec): landing page below the hero (#344 phase 2)
AndresL230 Aug 1, 2026
0691218
docs(plan): landing interactive graph, step 1 (#344)
AndresL230 Aug 1, 2026
651de5f
feat(landing): course graph fixtures for the interactive demo (#344)
AndresL230 Aug 1, 2026
f5b89b5
feat(landing): deterministic radial layout + helical entry path (#344)
AndresL230 Aug 1, 2026
d6fc3b9
feat(landing): knowledge graph demo — chips and laid-out render (#344)
AndresL230 Aug 1, 2026
69a718b
feat(landing): helical assembly, parked under reduced motion (#344)
AndresL230 Aug 1, 2026
fed4d12
fix(landing): SSR-safe reduced-motion detection for the graph demo (#…
AndresL230 Aug 1, 2026
fe906f9
test(landing): cold-start the reduced-motion store cache per test (#344)
AndresL230 Aug 1, 2026
1a3c2f5
feat(landing): graph hover blurbs and copy fade on engagement (#344)
AndresL230 Aug 1, 2026
d42d68b
feat(landing): replace HowItWorks and the feature catalog with the gr…
AndresL230 Aug 1, 2026
bb8ca16
test(e2e): journey for the landing knowledge graph (#344)
AndresL230 Aug 1, 2026
1e026e1
fix(landing): viewport-gate the graph assembly, never render it blank…
AndresL230 Aug 1, 2026
5ab1eda
fix(landing): brand-conform the graph section and fit its frame (#344)
AndresL230 Aug 1, 2026
b9e422e
fix(landing): take the outer ring off the horizontal axis (#344)
AndresL230 Aug 1, 2026
5631639
fix(landing): lay the graph out as a radial tree, not concentric ring…
AndresL230 Aug 1, 2026
fc0afe8
fix(landing): grow the graph upward from its base, not down from the …
AndresL230 Aug 2, 2026
5429aa6
fix(landing): tighten the graph's entry sweep to a quarter turn (#344)
AndresL230 Aug 2, 2026
86cfacf
fix(landing): restore atmospheric continuity between the sections (#344)
AndresL230 Aug 2, 2026
c99ed01
feat(landing): three feature bands and a bento of built surfaces (#344)
AndresL230 Aug 2, 2026
7e4ea24
feat(landing): give the knowledge graph product chrome, a dial and a …
AndresL230 Aug 2, 2026
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
62 changes: 62 additions & 0 deletions docs/frontend-testids.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -80,6 +80,10 @@ renders the element.
| Calendar | `calendar` | `frontend/src/components/screens/Calendar.tsx` (the `/calendar` screen — today just the #185 load-failure banner + retry) |
| Gradebook | `gradebook` | `frontend/src/components/screens/Gradebook/Landing.tsx` + `Course.tsx` (the `/gradebook` screens), `frontend/src/components/Gradebook/TranscriptModal.tsx` (the transcript modal), `frontend/src/components/Gradebook/CourseCard.tsx` (the term-aware card links), `frontend/src/components/Gradebook/AssignmentList.tsx` + `AssignmentModal.tsx` (the add-assignment flow the #468 mutation leg drives) — added with the #139 term-switcher/transcript journey |
| Admin analytics | `admin-analytics` | `frontend/src/components/screens/AdminAnalytics.tsx` (the `/admin/analytics` dashboard — range presets/inputs, cost group-by toggle, per-panel retry) — added with the #121 data layer |
| Landing knowledge graph demo | `landing-graph` | `frontend/src/components/marketing/graph/KnowledgeGraphDemo.tsx` (the pre-auth landing page's interactive course-picker + laid-out concept graph, wearing the `landing-surface` chrome with an inspector rail and mastery legend since #344 step 3) |
| Landing feature bands | `landing-band` | `frontend/src/components/marketing/FeatureBand.tsx` (the three full-width bands below the graph; content + side-alternation in `featureBands.tsx`, #344 step 2) |
| Landing surface bento | `landing-bento` | `frontend/src/components/marketing/SurfaceBento.tsx` (the four-tile grid of built product surfaces, #344 step 2) |
| Landing product surfaces | `landing-surface` | `frontend/src/components/marketing/surfaces/*.tsx` (the seven in-page recreations the bands and bento mount, #344 step 2) |

Two surfaces do **not** carry their testids in the screen file named by the
route:
Expand DownExpand Up@@ -324,6 +328,64 @@ Added with the upload → SSE → library journey (#387).
| `library-concepts-toggle-all` | detail panel "Expand all" / "Collapse all" |
| `library-concept-toggle-{idx}` | one concept accordion toggle (render index) |

### `landing-graph`

Added with the #344 landing-page knowledge-graph demo. Ships the static,
fully laid-out render (chips to switch course, nodes/edges for the selected
graph), the helical assembly animation, and hover interaction (detail-panel
swap + copy fade on first engagement); click-to-expand is a later task in the
same spec and will reuse these same testids.

Step 3 wrapped the whole thing in the same `landing-surface` chrome the bands
and bento use — a titled bar over a `--bg-mesh` canvas, an inspector rail and a
mastery legend — so the ids below split into the section's own controls and the
screen's panes.

| testid | element |
| --- | --- |
| `landing-graph` | demo section root (`aria-label` names the selected course) |
| `landing-graph-chip-{courseId}` | one course-picker chip, per `COURSE_GRAPHS` entry — `aria-pressed` marks the active course |
| `landing-graph-copy` | the section's instructional copy block (eyebrow label + heading) — `data-engaged` flips `"true"` on the visitor's first node hover and stays that way across course switches |
| `landing-graph-eyebrow` | the "Your knowledge, mapped" eyebrow label — deliberately *not* faded on engagement (WCAG AA at 0.7rem) |
| `landing-graph-headline` | the "Pick a course. Watch it grow." heading — carries the engagement fade (`ENGAGED_HEADLINE_OPACITY`) |
| `landing-graph-surface` | the product-chrome frame the whole screen sits in (`landing-surface`) |
| `landing-graph-meta` | the chrome bar's right-hand status — `{code} · {conceptCount} concepts · {n}% mastery`, all read off the selected `CourseGraph` |
| `landing-graph-svg` | the `<svg>` itself — its `viewBox` is the desktop or the phone `GraphView` (`graph/layout.ts`), swapped at the mobile breakpoint |
| `landing-graph-node-{nodeId}` | one SVG node group (`<g>`) in the selected course's graph. Its **first `<circle>` is the tier-painted disc at the full node radius** — the phone legibility gate measures that element, so nothing may be drawn in front of it |
| `landing-graph-detail` | the inspector rail's frame. Never empty: with nothing hovered it shows the course (the root node) |
| `landing-graph-detail-name` | the inspected node's name |
| `landing-graph-detail-tier` | its mastery tier as a labelled chip (`Course` for the root, which is an anchor rather than a status) |
| `landing-graph-detail-mastery` | its mastery as a percentage |
| `landing-graph-blurb` | the reserved-height paragraph in the panel that shows the inspected node's one-sentence blurb |
| `landing-graph-legend` | the mastery legend pinned to the foot of the rail |
| `landing-graph-legend-{tier}` | one legend row — `mastered`, `learning`, `struggling` or `unexplored`, each with the tier's own count of drawn concepts |

### `landing-band` / `landing-bento` / `landing-surface`

Added with the #344 step-2 build (feature bands + surface bento). These
surfaces are **static recreations** of shipped product screens — there is
deliberately not a single `<button>`, `<input>` or `<textarea>` among them
(the spec's "no per-tile buttons"), so the lint block below is satisfied
vacuously and stays as a guard on anything added later.

| testid | element |
| --- | --- |
| `landing-band-{bandId}` | one feature band's `<section>` — `bandId` is `upload`, `quiz` or `review` (a stable content id, per the repeated-items rule). Carries `data-surface-side="left"\|"right"`, the resolved alternation |
| `landing-band-{bandId}-eyebrow` | that band's mono micro-label |
| `landing-bento` | the four-tile grid's `<section>` root |
| `landing-bento-eyebrow` | the bento's mono micro-label |
| `landing-surface-upload` | Document Library ingest queue (band 1) |
| `landing-surface-quiz` | an Adaptive Quizzes question mid-answer (band 2) |
| `landing-surface-review` | the Study screen's spaced review + rating trio (band 3) |
| `landing-surface-tutor` | a Tutor exchange (bento) |
| `landing-surface-notes` | a Notetaker note with its linked concepts (bento) |
| `landing-surface-rooms` | a Study Rooms conversation (bento) |
| `landing-surface-gradebook` | a Gradebook course header + assignment rows (bento) |

The `landing-surface-*` ids suffix the *surface* rather than a render index
because each one mounts exactly once and the surface name is the stable
domain id here.

## Enforcement

`frontend/eslint.config.mjs` has a per-file `no-restricted-syntax` block scoped
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(landing): interactive knowledge graph replaces the scroll section (#344 phase 2) by AndresL230 · Pull Request #513 · SaplingLearn/Sapling · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
50c3311
docs(spec): landing page below the hero (#344 phase 2)
AndresL230 Aug 1, 2026
0691218
docs(plan): landing interactive graph, step 1 (#344)
AndresL230 Aug 1, 2026
651de5f
feat(landing): course graph fixtures for the interactive demo (#344)
AndresL230 Aug 1, 2026
f5b89b5
feat(landing): deterministic radial layout + helical entry path (#344)
AndresL230 Aug 1, 2026
d6fc3b9
feat(landing): knowledge graph demo — chips and laid-out render (#344)
AndresL230 Aug 1, 2026
69a718b
feat(landing): helical assembly, parked under reduced motion (#344)
AndresL230 Aug 1, 2026
fed4d12
fix(landing): SSR-safe reduced-motion detection for the graph demo (#…
AndresL230 Aug 1, 2026
fe906f9
test(landing): cold-start the reduced-motion store cache per test (#344)
AndresL230 Aug 1, 2026
1a3c2f5
feat(landing): graph hover blurbs and copy fade on engagement (#344)
AndresL230 Aug 1, 2026
d42d68b
feat(landing): replace HowItWorks and the feature catalog with the gr…
AndresL230 Aug 1, 2026
bb8ca16
test(e2e): journey for the landing knowledge graph (#344)
AndresL230 Aug 1, 2026
1e026e1
fix(landing): viewport-gate the graph assembly, never render it blank…
AndresL230 Aug 1, 2026
5ab1eda
fix(landing): brand-conform the graph section and fit its frame (#344)
AndresL230 Aug 1, 2026
b9e422e
fix(landing): take the outer ring off the horizontal axis (#344)
AndresL230 Aug 1, 2026
5631639
fix(landing): lay the graph out as a radial tree, not concentric ring…
AndresL230 Aug 1, 2026
fc0afe8
fix(landing): grow the graph upward from its base, not down from the …
AndresL230 Aug 2, 2026
5429aa6
fix(landing): tighten the graph's entry sweep to a quarter turn (#344)
AndresL230 Aug 2, 2026
86cfacf
fix(landing): restore atmospheric continuity between the sections (#344)
AndresL230 Aug 2, 2026
c99ed01
feat(landing): three feature bands and a bento of built surfaces (#344)
AndresL230 Aug 2, 2026
7e4ea24
feat(landing): give the knowledge graph product chrome, a dial and a …
AndresL230 Aug 2, 2026
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
62 changes: 62 additions & 0 deletions docs/frontend-testids.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -80,6 +80,10 @@ renders the element.
| Calendar | `calendar` | `frontend/src/components/screens/Calendar.tsx` (the `/calendar` screen — today just the #185 load-failure banner + retry) |
| Gradebook | `gradebook` | `frontend/src/components/screens/Gradebook/Landing.tsx` + `Course.tsx` (the `/gradebook` screens), `frontend/src/components/Gradebook/TranscriptModal.tsx` (the transcript modal), `frontend/src/components/Gradebook/CourseCard.tsx` (the term-aware card links), `frontend/src/components/Gradebook/AssignmentList.tsx` + `AssignmentModal.tsx` (the add-assignment flow the #468 mutation leg drives) — added with the #139 term-switcher/transcript journey |
| Admin analytics | `admin-analytics` | `frontend/src/components/screens/AdminAnalytics.tsx` (the `/admin/analytics` dashboard — range presets/inputs, cost group-by toggle, per-panel retry) — added with the #121 data layer |
| Landing knowledge graph demo | `landing-graph` | `frontend/src/components/marketing/graph/KnowledgeGraphDemo.tsx` (the pre-auth landing page's interactive course-picker + laid-out concept graph, wearing the `landing-surface` chrome with an inspector rail and mastery legend since #344 step 3) |
| Landing feature bands | `landing-band` | `frontend/src/components/marketing/FeatureBand.tsx` (the three full-width bands below the graph; content + side-alternation in `featureBands.tsx`, #344 step 2) |
| Landing surface bento | `landing-bento` | `frontend/src/components/marketing/SurfaceBento.tsx` (the four-tile grid of built product surfaces, #344 step 2) |
| Landing product surfaces | `landing-surface` | `frontend/src/components/marketing/surfaces/*.tsx` (the seven in-page recreations the bands and bento mount, #344 step 2) |

Two surfaces do **not** carry their testids in the screen file named by the
route:
Expand DownExpand Up@@ -324,6 +328,64 @@ Added with the upload → SSE → library journey (#387).
| `library-concepts-toggle-all` | detail panel "Expand all" / "Collapse all" |
| `library-concept-toggle-{idx}` | one concept accordion toggle (render index) |

### `landing-graph`

Added with the #344 landing-page knowledge-graph demo. Ships the static,
fully laid-out render (chips to switch course, nodes/edges for the selected
graph), the helical assembly animation, and hover interaction (detail-panel
swap + copy fade on first engagement); click-to-expand is a later task in the
same spec and will reuse these same testids.

Step 3 wrapped the whole thing in the same `landing-surface` chrome the bands
and bento use — a titled bar over a `--bg-mesh` canvas, an inspector rail and a
mastery legend — so the ids below split into the section's own controls and the
screen's panes.

| testid | element |
| --- | --- |
| `landing-graph` | demo section root (`aria-label` names the selected course) |
| `landing-graph-chip-{courseId}` | one course-picker chip, per `COURSE_GRAPHS` entry — `aria-pressed` marks the active course |
| `landing-graph-copy` | the section's instructional copy block (eyebrow label + heading) — `data-engaged` flips `"true"` on the visitor's first node hover and stays that way across course switches |
| `landing-graph-eyebrow` | the "Your knowledge, mapped" eyebrow label — deliberately *not* faded on engagement (WCAG AA at 0.7rem) |
| `landing-graph-headline` | the "Pick a course. Watch it grow." heading — carries the engagement fade (`ENGAGED_HEADLINE_OPACITY`) |
| `landing-graph-surface` | the product-chrome frame the whole screen sits in (`landing-surface`) |
| `landing-graph-meta` | the chrome bar's right-hand status — `{code} · {conceptCount} concepts · {n}% mastery`, all read off the selected `CourseGraph` |
| `landing-graph-svg` | the `<svg>` itself — its `viewBox` is the desktop or the phone `GraphView` (`graph/layout.ts`), swapped at the mobile breakpoint |
| `landing-graph-node-{nodeId}` | one SVG node group (`<g>`) in the selected course's graph. Its **first `<circle>` is the tier-painted disc at the full node radius** — the phone legibility gate measures that element, so nothing may be drawn in front of it |
| `landing-graph-detail` | the inspector rail's frame. Never empty: with nothing hovered it shows the course (the root node) |
| `landing-graph-detail-name` | the inspected node's name |
| `landing-graph-detail-tier` | its mastery tier as a labelled chip (`Course` for the root, which is an anchor rather than a status) |
| `landing-graph-detail-mastery` | its mastery as a percentage |
| `landing-graph-blurb` | the reserved-height paragraph in the panel that shows the inspected node's one-sentence blurb |
| `landing-graph-legend` | the mastery legend pinned to the foot of the rail |
| `landing-graph-legend-{tier}` | one legend row — `mastered`, `learning`, `struggling` or `unexplored`, each with the tier's own count of drawn concepts |

### `landing-band` / `landing-bento` / `landing-surface`

Added with the #344 step-2 build (feature bands + surface bento). These
surfaces are **static recreations** of shipped product screens — there is
deliberately not a single `<button>`, `<input>` or `<textarea>` among them
(the spec's "no per-tile buttons"), so the lint block below is satisfied
vacuously and stays as a guard on anything added later.

| testid | element |
| --- | --- |
| `landing-band-{bandId}` | one feature band's `<section>` — `bandId` is `upload`, `quiz` or `review` (a stable content id, per the repeated-items rule). Carries `data-surface-side="left"\|"right"`, the resolved alternation |
| `landing-band-{bandId}-eyebrow` | that band's mono micro-label |
| `landing-bento` | the four-tile grid's `<section>` root |
| `landing-bento-eyebrow` | the bento's mono micro-label |
| `landing-surface-upload` | Document Library ingest queue (band 1) |
| `landing-surface-quiz` | an Adaptive Quizzes question mid-answer (band 2) |
| `landing-surface-review` | the Study screen's spaced review + rating trio (band 3) |
| `landing-surface-tutor` | a Tutor exchange (bento) |
| `landing-surface-notes` | a Notetaker note with its linked concepts (bento) |
| `landing-surface-rooms` | a Study Rooms conversation (bento) |
| `landing-surface-gradebook` | a Gradebook course header + assignment rows (bento) |

The `landing-surface-*` ids suffix the *surface* rather than a render index
because each one mounts exactly once and the surface name is the stable
domain id here.

## Enforcement

`frontend/eslint.config.mjs` has a per-file `no-restricted-syntax` block scoped
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(landing): interactive knowledge graph replaces the scroll section (#344 phase 2) by AndresL230 · Pull Request #513 · SaplingLearn/Sapling · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
50c3311
docs(spec): landing page below the hero (#344 phase 2)
AndresL230 Aug 1, 2026
0691218
docs(plan): landing interactive graph, step 1 (#344)
AndresL230 Aug 1, 2026
651de5f
feat(landing): course graph fixtures for the interactive demo (#344)
AndresL230 Aug 1, 2026
f5b89b5
feat(landing): deterministic radial layout + helical entry path (#344)
AndresL230 Aug 1, 2026
d6fc3b9
feat(landing): knowledge graph demo — chips and laid-out render (#344)
AndresL230 Aug 1, 2026
69a718b
feat(landing): helical assembly, parked under reduced motion (#344)
AndresL230 Aug 1, 2026
fed4d12
fix(landing): SSR-safe reduced-motion detection for the graph demo (#…
AndresL230 Aug 1, 2026
fe906f9
test(landing): cold-start the reduced-motion store cache per test (#344)
AndresL230 Aug 1, 2026
1a3c2f5
feat(landing): graph hover blurbs and copy fade on engagement (#344)
AndresL230 Aug 1, 2026
d42d68b
feat(landing): replace HowItWorks and the feature catalog with the gr…
AndresL230 Aug 1, 2026
bb8ca16
test(e2e): journey for the landing knowledge graph (#344)
AndresL230 Aug 1, 2026
1e026e1
fix(landing): viewport-gate the graph assembly, never render it blank…
AndresL230 Aug 1, 2026
5ab1eda
fix(landing): brand-conform the graph section and fit its frame (#344)
AndresL230 Aug 1, 2026
b9e422e
fix(landing): take the outer ring off the horizontal axis (#344)
AndresL230 Aug 1, 2026
5631639
fix(landing): lay the graph out as a radial tree, not concentric ring…
AndresL230 Aug 1, 2026
fc0afe8
fix(landing): grow the graph upward from its base, not down from the …
AndresL230 Aug 2, 2026
5429aa6
fix(landing): tighten the graph's entry sweep to a quarter turn (#344)
AndresL230 Aug 2, 2026
86cfacf
fix(landing): restore atmospheric continuity between the sections (#344)
AndresL230 Aug 2, 2026
c99ed01
feat(landing): three feature bands and a bento of built surfaces (#344)
AndresL230 Aug 2, 2026
7e4ea24
feat(landing): give the knowledge graph product chrome, a dial and a …
AndresL230 Aug 2, 2026
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
62 changes: 62 additions & 0 deletions docs/frontend-testids.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -80,6 +80,10 @@ renders the element.
| Calendar | `calendar` | `frontend/src/components/screens/Calendar.tsx` (the `/calendar` screen — today just the #185 load-failure banner + retry) |
| Gradebook | `gradebook` | `frontend/src/components/screens/Gradebook/Landing.tsx` + `Course.tsx` (the `/gradebook` screens), `frontend/src/components/Gradebook/TranscriptModal.tsx` (the transcript modal), `frontend/src/components/Gradebook/CourseCard.tsx` (the term-aware card links), `frontend/src/components/Gradebook/AssignmentList.tsx` + `AssignmentModal.tsx` (the add-assignment flow the #468 mutation leg drives) — added with the #139 term-switcher/transcript journey |
| Admin analytics | `admin-analytics` | `frontend/src/components/screens/AdminAnalytics.tsx` (the `/admin/analytics` dashboard — range presets/inputs, cost group-by toggle, per-panel retry) — added with the #121 data layer |
| Landing knowledge graph demo | `landing-graph` | `frontend/src/components/marketing/graph/KnowledgeGraphDemo.tsx` (the pre-auth landing page's interactive course-picker + laid-out concept graph, wearing the `landing-surface` chrome with an inspector rail and mastery legend since #344 step 3) |
| Landing feature bands | `landing-band` | `frontend/src/components/marketing/FeatureBand.tsx` (the three full-width bands below the graph; content + side-alternation in `featureBands.tsx`, #344 step 2) |
| Landing surface bento | `landing-bento` | `frontend/src/components/marketing/SurfaceBento.tsx` (the four-tile grid of built product surfaces, #344 step 2) |
| Landing product surfaces | `landing-surface` | `frontend/src/components/marketing/surfaces/*.tsx` (the seven in-page recreations the bands and bento mount, #344 step 2) |

Two surfaces do **not** carry their testids in the screen file named by the
route:
Expand DownExpand Up@@ -324,6 +328,64 @@ Added with the upload → SSE → library journey (#387).
| `library-concepts-toggle-all` | detail panel "Expand all" / "Collapse all" |
| `library-concept-toggle-{idx}` | one concept accordion toggle (render index) |

### `landing-graph`

Added with the #344 landing-page knowledge-graph demo. Ships the static,
fully laid-out render (chips to switch course, nodes/edges for the selected
graph), the helical assembly animation, and hover interaction (detail-panel
swap + copy fade on first engagement); click-to-expand is a later task in the
same spec and will reuse these same testids.

Step 3 wrapped the whole thing in the same `landing-surface` chrome the bands
and bento use — a titled bar over a `--bg-mesh` canvas, an inspector rail and a
mastery legend — so the ids below split into the section's own controls and the
screen's panes.

| testid | element |
| --- | --- |
| `landing-graph` | demo section root (`aria-label` names the selected course) |
| `landing-graph-chip-{courseId}` | one course-picker chip, per `COURSE_GRAPHS` entry — `aria-pressed` marks the active course |
| `landing-graph-copy` | the section's instructional copy block (eyebrow label + heading) — `data-engaged` flips `"true"` on the visitor's first node hover and stays that way across course switches |
| `landing-graph-eyebrow` | the "Your knowledge, mapped" eyebrow label — deliberately *not* faded on engagement (WCAG AA at 0.7rem) |
| `landing-graph-headline` | the "Pick a course. Watch it grow." heading — carries the engagement fade (`ENGAGED_HEADLINE_OPACITY`) |
| `landing-graph-surface` | the product-chrome frame the whole screen sits in (`landing-surface`) |
| `landing-graph-meta` | the chrome bar's right-hand status — `{code} · {conceptCount} concepts · {n}% mastery`, all read off the selected `CourseGraph` |
| `landing-graph-svg` | the `<svg>` itself — its `viewBox` is the desktop or the phone `GraphView` (`graph/layout.ts`), swapped at the mobile breakpoint |
| `landing-graph-node-{nodeId}` | one SVG node group (`<g>`) in the selected course's graph. Its **first `<circle>` is the tier-painted disc at the full node radius** — the phone legibility gate measures that element, so nothing may be drawn in front of it |
| `landing-graph-detail` | the inspector rail's frame. Never empty: with nothing hovered it shows the course (the root node) |
| `landing-graph-detail-name` | the inspected node's name |
| `landing-graph-detail-tier` | its mastery tier as a labelled chip (`Course` for the root, which is an anchor rather than a status) |
| `landing-graph-detail-mastery` | its mastery as a percentage |
| `landing-graph-blurb` | the reserved-height paragraph in the panel that shows the inspected node's one-sentence blurb |
| `landing-graph-legend` | the mastery legend pinned to the foot of the rail |
| `landing-graph-legend-{tier}` | one legend row — `mastered`, `learning`, `struggling` or `unexplored`, each with the tier's own count of drawn concepts |

### `landing-band` / `landing-bento` / `landing-surface`

Added with the #344 step-2 build (feature bands + surface bento). These
surfaces are **static recreations** of shipped product screens — there is
deliberately not a single `<button>`, `<input>` or `<textarea>` among them
(the spec's "no per-tile buttons"), so the lint block below is satisfied
vacuously and stays as a guard on anything added later.

| testid | element |
| --- | --- |
| `landing-band-{bandId}` | one feature band's `<section>` — `bandId` is `upload`, `quiz` or `review` (a stable content id, per the repeated-items rule). Carries `data-surface-side="left"\|"right"`, the resolved alternation |
| `landing-band-{bandId}-eyebrow` | that band's mono micro-label |
| `landing-bento` | the four-tile grid's `<section>` root |
| `landing-bento-eyebrow` | the bento's mono micro-label |
| `landing-surface-upload` | Document Library ingest queue (band 1) |
| `landing-surface-quiz` | an Adaptive Quizzes question mid-answer (band 2) |
| `landing-surface-review` | the Study screen's spaced review + rating trio (band 3) |
| `landing-surface-tutor` | a Tutor exchange (bento) |
| `landing-surface-notes` | a Notetaker note with its linked concepts (bento) |
| `landing-surface-rooms` | a Study Rooms conversation (bento) |
| `landing-surface-gradebook` | a Gradebook course header + assignment rows (bento) |

The `landing-surface-*` ids suffix the *surface* rather than a render index
because each one mounts exactly once and the surface name is the stable
domain id here.

## Enforcement

`frontend/eslint.config.mjs` has a per-file `no-restricted-syntax` block scoped
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' feat(landing): interactive knowledge graph replaces the scroll section (#344 phase 2) by AndresL230 · Pull Request #513 · SaplingLearn/Sapling · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
50c3311
docs(spec): landing page below the hero (#344 phase 2)
AndresL230 Aug 1, 2026
0691218
docs(plan): landing interactive graph, step 1 (#344)
AndresL230 Aug 1, 2026
651de5f
feat(landing): course graph fixtures for the interactive demo (#344)
AndresL230 Aug 1, 2026
f5b89b5
feat(landing): deterministic radial layout + helical entry path (#344)
AndresL230 Aug 1, 2026
d6fc3b9
feat(landing): knowledge graph demo — chips and laid-out render (#344)
AndresL230 Aug 1, 2026
69a718b
feat(landing): helical assembly, parked under reduced motion (#344)
AndresL230 Aug 1, 2026
fed4d12
fix(landing): SSR-safe reduced-motion detection for the graph demo (#…
AndresL230 Aug 1, 2026
fe906f9
test(landing): cold-start the reduced-motion store cache per test (#344)
AndresL230 Aug 1, 2026
1a3c2f5
feat(landing): graph hover blurbs and copy fade on engagement (#344)
AndresL230 Aug 1, 2026
d42d68b
feat(landing): replace HowItWorks and the feature catalog with the gr…
AndresL230 Aug 1, 2026
bb8ca16
test(e2e): journey for the landing knowledge graph (#344)
AndresL230 Aug 1, 2026
1e026e1
fix(landing): viewport-gate the graph assembly, never render it blank…
AndresL230 Aug 1, 2026
5ab1eda
fix(landing): brand-conform the graph section and fit its frame (#344)
AndresL230 Aug 1, 2026
b9e422e
fix(landing): take the outer ring off the horizontal axis (#344)
AndresL230 Aug 1, 2026
5631639
fix(landing): lay the graph out as a radial tree, not concentric ring…
AndresL230 Aug 1, 2026
fc0afe8
fix(landing): grow the graph upward from its base, not down from the …
AndresL230 Aug 2, 2026
5429aa6
fix(landing): tighten the graph's entry sweep to a quarter turn (#344)
AndresL230 Aug 2, 2026
86cfacf
fix(landing): restore atmospheric continuity between the sections (#344)
AndresL230 Aug 2, 2026
c99ed01
feat(landing): three feature bands and a bento of built surfaces (#344)
AndresL230 Aug 2, 2026
7e4ea24
feat(landing): give the knowledge graph product chrome, a dial and a …
AndresL230 Aug 2, 2026
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
62 changes: 62 additions & 0 deletions docs/frontend-testids.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -80,6 +80,10 @@ renders the element.
| Calendar | `calendar` | `frontend/src/components/screens/Calendar.tsx` (the `/calendar` screen — today just the #185 load-failure banner + retry) |
| Gradebook | `gradebook` | `frontend/src/components/screens/Gradebook/Landing.tsx` + `Course.tsx` (the `/gradebook` screens), `frontend/src/components/Gradebook/TranscriptModal.tsx` (the transcript modal), `frontend/src/components/Gradebook/CourseCard.tsx` (the term-aware card links), `frontend/src/components/Gradebook/AssignmentList.tsx` + `AssignmentModal.tsx` (the add-assignment flow the #468 mutation leg drives) — added with the #139 term-switcher/transcript journey |
| Admin analytics | `admin-analytics` | `frontend/src/components/screens/AdminAnalytics.tsx` (the `/admin/analytics` dashboard — range presets/inputs, cost group-by toggle, per-panel retry) — added with the #121 data layer |
| Landing knowledge graph demo | `landing-graph` | `frontend/src/components/marketing/graph/KnowledgeGraphDemo.tsx` (the pre-auth landing page's interactive course-picker + laid-out concept graph, wearing the `landing-surface` chrome with an inspector rail and mastery legend since #344 step 3) |
| Landing feature bands | `landing-band` | `frontend/src/components/marketing/FeatureBand.tsx` (the three full-width bands below the graph; content + side-alternation in `featureBands.tsx`, #344 step 2) |
| Landing surface bento | `landing-bento` | `frontend/src/components/marketing/SurfaceBento.tsx` (the four-tile grid of built product surfaces, #344 step 2) |
| Landing product surfaces | `landing-surface` | `frontend/src/components/marketing/surfaces/*.tsx` (the seven in-page recreations the bands and bento mount, #344 step 2) |

Two surfaces do **not** carry their testids in the screen file named by the
route:
Expand DownExpand Up@@ -324,6 +328,64 @@ Added with the upload → SSE → library journey (#387).
| `library-concepts-toggle-all` | detail panel "Expand all" / "Collapse all" |
| `library-concept-toggle-{idx}` | one concept accordion toggle (render index) |

### `landing-graph`

Added with the #344 landing-page knowledge-graph demo. Ships the static,
fully laid-out render (chips to switch course, nodes/edges for the selected
graph), the helical assembly animation, and hover interaction (detail-panel
swap + copy fade on first engagement); click-to-expand is a later task in the
same spec and will reuse these same testids.

Step 3 wrapped the whole thing in the same `landing-surface` chrome the bands
and bento use — a titled bar over a `--bg-mesh` canvas, an inspector rail and a
mastery legend — so the ids below split into the section's own controls and the
screen's panes.

| testid | element |
| --- | --- |
| `landing-graph` | demo section root (`aria-label` names the selected course) |
| `landing-graph-chip-{courseId}` | one course-picker chip, per `COURSE_GRAPHS` entry — `aria-pressed` marks the active course |
| `landing-graph-copy` | the section's instructional copy block (eyebrow label + heading) — `data-engaged` flips `"true"` on the visitor's first node hover and stays that way across course switches |
| `landing-graph-eyebrow` | the "Your knowledge, mapped" eyebrow label — deliberately *not* faded on engagement (WCAG AA at 0.7rem) |
| `landing-graph-headline` | the "Pick a course. Watch it grow." heading — carries the engagement fade (`ENGAGED_HEADLINE_OPACITY`) |
| `landing-graph-surface` | the product-chrome frame the whole screen sits in (`landing-surface`) |
| `landing-graph-meta` | the chrome bar's right-hand status — `{code} · {conceptCount} concepts · {n}% mastery`, all read off the selected `CourseGraph` |
| `landing-graph-svg` | the `<svg>` itself — its `viewBox` is the desktop or the phone `GraphView` (`graph/layout.ts`), swapped at the mobile breakpoint |
| `landing-graph-node-{nodeId}` | one SVG node group (`<g>`) in the selected course's graph. Its **first `<circle>` is the tier-painted disc at the full node radius** — the phone legibility gate measures that element, so nothing may be drawn in front of it |
| `landing-graph-detail` | the inspector rail's frame. Never empty: with nothing hovered it shows the course (the root node) |
| `landing-graph-detail-name` | the inspected node's name |
| `landing-graph-detail-tier` | its mastery tier as a labelled chip (`Course` for the root, which is an anchor rather than a status) |
| `landing-graph-detail-mastery` | its mastery as a percentage |
| `landing-graph-blurb` | the reserved-height paragraph in the panel that shows the inspected node's one-sentence blurb |
| `landing-graph-legend` | the mastery legend pinned to the foot of the rail |
| `landing-graph-legend-{tier}` | one legend row — `mastered`, `learning`, `struggling` or `unexplored`, each with the tier's own count of drawn concepts |

### `landing-band` / `landing-bento` / `landing-surface`

Added with the #344 step-2 build (feature bands + surface bento). These
surfaces are **static recreations** of shipped product screens — there is
deliberately not a single `<button>`, `<input>` or `<textarea>` among them
(the spec's "no per-tile buttons"), so the lint block below is satisfied
vacuously and stays as a guard on anything added later.

| testid | element |
| --- | --- |
| `landing-band-{bandId}` | one feature band's `<section>` — `bandId` is `upload`, `quiz` or `review` (a stable content id, per the repeated-items rule). Carries `data-surface-side="left"\|"right"`, the resolved alternation |
| `landing-band-{bandId}-eyebrow` | that band's mono micro-label |
| `landing-bento` | the four-tile grid's `<section>` root |
| `landing-bento-eyebrow` | the bento's mono micro-label |
| `landing-surface-upload` | Document Library ingest queue (band 1) |
| `landing-surface-quiz` | an Adaptive Quizzes question mid-answer (band 2) |
| `landing-surface-review` | the Study screen's spaced review + rating trio (band 3) |
| `landing-surface-tutor` | a Tutor exchange (bento) |
| `landing-surface-notes` | a Notetaker note with its linked concepts (bento) |
| `landing-surface-rooms` | a Study Rooms conversation (bento) |
| `landing-surface-gradebook` | a Gradebook course header + assignment rows (bento) |

The `landing-surface-*` ids suffix the *surface* rather than a render index
because each one mounts exactly once and the surface name is the stable
domain id here.

## Enforcement

`frontend/eslint.config.mjs` has a per-file `no-restricted-syntax` block scoped
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(landing): interactive knowledge graph replaces the scroll section (#344 phase 2) by AndresL230 · Pull Request #513 · SaplingLearn/Sapling · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
50c3311
docs(spec): landing page below the hero (#344 phase 2)
AndresL230 Aug 1, 2026
0691218
docs(plan): landing interactive graph, step 1 (#344)
AndresL230 Aug 1, 2026
651de5f
feat(landing): course graph fixtures for the interactive demo (#344)
AndresL230 Aug 1, 2026
f5b89b5
feat(landing): deterministic radial layout + helical entry path (#344)
AndresL230 Aug 1, 2026
d6fc3b9
feat(landing): knowledge graph demo — chips and laid-out render (#344)
AndresL230 Aug 1, 2026
69a718b
feat(landing): helical assembly, parked under reduced motion (#344)
AndresL230 Aug 1, 2026
fed4d12
fix(landing): SSR-safe reduced-motion detection for the graph demo (#…
AndresL230 Aug 1, 2026
fe906f9
test(landing): cold-start the reduced-motion store cache per test (#344)
AndresL230 Aug 1, 2026
1a3c2f5
feat(landing): graph hover blurbs and copy fade on engagement (#344)
AndresL230 Aug 1, 2026
d42d68b
feat(landing): replace HowItWorks and the feature catalog with the gr…
AndresL230 Aug 1, 2026
bb8ca16
test(e2e): journey for the landing knowledge graph (#344)
AndresL230 Aug 1, 2026
1e026e1
fix(landing): viewport-gate the graph assembly, never render it blank…
AndresL230 Aug 1, 2026
5ab1eda
fix(landing): brand-conform the graph section and fit its frame (#344)
AndresL230 Aug 1, 2026
b9e422e
fix(landing): take the outer ring off the horizontal axis (#344)
AndresL230 Aug 1, 2026
5631639
fix(landing): lay the graph out as a radial tree, not concentric ring…
AndresL230 Aug 1, 2026
fc0afe8
fix(landing): grow the graph upward from its base, not down from the …
AndresL230 Aug 2, 2026
5429aa6
fix(landing): tighten the graph's entry sweep to a quarter turn (#344)
AndresL230 Aug 2, 2026
86cfacf
fix(landing): restore atmospheric continuity between the sections (#344)
AndresL230 Aug 2, 2026
c99ed01
feat(landing): three feature bands and a bento of built surfaces (#344)
AndresL230 Aug 2, 2026
7e4ea24
feat(landing): give the knowledge graph product chrome, a dial and a …
AndresL230 Aug 2, 2026
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
62 changes: 62 additions & 0 deletions docs/frontend-testids.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -80,6 +80,10 @@ renders the element.
| Calendar | `calendar` | `frontend/src/components/screens/Calendar.tsx` (the `/calendar` screen — today just the #185 load-failure banner + retry) |
| Gradebook | `gradebook` | `frontend/src/components/screens/Gradebook/Landing.tsx` + `Course.tsx` (the `/gradebook` screens), `frontend/src/components/Gradebook/TranscriptModal.tsx` (the transcript modal), `frontend/src/components/Gradebook/CourseCard.tsx` (the term-aware card links), `frontend/src/components/Gradebook/AssignmentList.tsx` + `AssignmentModal.tsx` (the add-assignment flow the #468 mutation leg drives) — added with the #139 term-switcher/transcript journey |
| Admin analytics | `admin-analytics` | `frontend/src/components/screens/AdminAnalytics.tsx` (the `/admin/analytics` dashboard — range presets/inputs, cost group-by toggle, per-panel retry) — added with the #121 data layer |
| Landing knowledge graph demo | `landing-graph` | `frontend/src/components/marketing/graph/KnowledgeGraphDemo.tsx` (the pre-auth landing page's interactive course-picker + laid-out concept graph, wearing the `landing-surface` chrome with an inspector rail and mastery legend since #344 step 3) |
| Landing feature bands | `landing-band` | `frontend/src/components/marketing/FeatureBand.tsx` (the three full-width bands below the graph; content + side-alternation in `featureBands.tsx`, #344 step 2) |
| Landing surface bento | `landing-bento` | `frontend/src/components/marketing/SurfaceBento.tsx` (the four-tile grid of built product surfaces, #344 step 2) |
| Landing product surfaces | `landing-surface` | `frontend/src/components/marketing/surfaces/*.tsx` (the seven in-page recreations the bands and bento mount, #344 step 2) |

Two surfaces do **not** carry their testids in the screen file named by the
route:
Expand DownExpand Up@@ -324,6 +328,64 @@ Added with the upload → SSE → library journey (#387).
| `library-concepts-toggle-all` | detail panel "Expand all" / "Collapse all" |
| `library-concept-toggle-{idx}` | one concept accordion toggle (render index) |

### `landing-graph`

Added with the #344 landing-page knowledge-graph demo. Ships the static,
fully laid-out render (chips to switch course, nodes/edges for the selected
graph), the helical assembly animation, and hover interaction (detail-panel
swap + copy fade on first engagement); click-to-expand is a later task in the
same spec and will reuse these same testids.

Step 3 wrapped the whole thing in the same `landing-surface` chrome the bands
and bento use — a titled bar over a `--bg-mesh` canvas, an inspector rail and a
mastery legend — so the ids below split into the section's own controls and the
screen's panes.

| testid | element |
| --- | --- |
| `landing-graph` | demo section root (`aria-label` names the selected course) |
| `landing-graph-chip-{courseId}` | one course-picker chip, per `COURSE_GRAPHS` entry — `aria-pressed` marks the active course |
| `landing-graph-copy` | the section's instructional copy block (eyebrow label + heading) — `data-engaged` flips `"true"` on the visitor's first node hover and stays that way across course switches |
| `landing-graph-eyebrow` | the "Your knowledge, mapped" eyebrow label — deliberately *not* faded on engagement (WCAG AA at 0.7rem) |
| `landing-graph-headline` | the "Pick a course. Watch it grow." heading — carries the engagement fade (`ENGAGED_HEADLINE_OPACITY`) |
| `landing-graph-surface` | the product-chrome frame the whole screen sits in (`landing-surface`) |
| `landing-graph-meta` | the chrome bar's right-hand status — `{code} · {conceptCount} concepts · {n}% mastery`, all read off the selected `CourseGraph` |
| `landing-graph-svg` | the `<svg>` itself — its `viewBox` is the desktop or the phone `GraphView` (`graph/layout.ts`), swapped at the mobile breakpoint |
| `landing-graph-node-{nodeId}` | one SVG node group (`<g>`) in the selected course's graph. Its **first `<circle>` is the tier-painted disc at the full node radius** — the phone legibility gate measures that element, so nothing may be drawn in front of it |
| `landing-graph-detail` | the inspector rail's frame. Never empty: with nothing hovered it shows the course (the root node) |
| `landing-graph-detail-name` | the inspected node's name |
| `landing-graph-detail-tier` | its mastery tier as a labelled chip (`Course` for the root, which is an anchor rather than a status) |
| `landing-graph-detail-mastery` | its mastery as a percentage |
| `landing-graph-blurb` | the reserved-height paragraph in the panel that shows the inspected node's one-sentence blurb |
| `landing-graph-legend` | the mastery legend pinned to the foot of the rail |
| `landing-graph-legend-{tier}` | one legend row — `mastered`, `learning`, `struggling` or `unexplored`, each with the tier's own count of drawn concepts |

### `landing-band` / `landing-bento` / `landing-surface`

Added with the #344 step-2 build (feature bands + surface bento). These
surfaces are **static recreations** of shipped product screens — there is
deliberately not a single `<button>`, `<input>` or `<textarea>` among them
(the spec's "no per-tile buttons"), so the lint block below is satisfied
vacuously and stays as a guard on anything added later.

| testid | element |
| --- | --- |
| `landing-band-{bandId}` | one feature band's `<section>` — `bandId` is `upload`, `quiz` or `review` (a stable content id, per the repeated-items rule). Carries `data-surface-side="left"\|"right"`, the resolved alternation |
| `landing-band-{bandId}-eyebrow` | that band's mono micro-label |
| `landing-bento` | the four-tile grid's `<section>` root |
| `landing-bento-eyebrow` | the bento's mono micro-label |
| `landing-surface-upload` | Document Library ingest queue (band 1) |
| `landing-surface-quiz` | an Adaptive Quizzes question mid-answer (band 2) |
| `landing-surface-review` | the Study screen's spaced review + rating trio (band 3) |
| `landing-surface-tutor` | a Tutor exchange (bento) |
| `landing-surface-notes` | a Notetaker note with its linked concepts (bento) |
| `landing-surface-rooms` | a Study Rooms conversation (bento) |
| `landing-surface-gradebook` | a Gradebook course header + assignment rows (bento) |

The `landing-surface-*` ids suffix the *surface* rather than a render index
because each one mounts exactly once and the surface name is the stable
domain id here.

## Enforcement

`frontend/eslint.config.mjs` has a per-file `no-restricted-syntax` block scoped
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(landing): interactive knowledge graph replaces the scroll section (#344 phase 2) by AndresL230 · Pull Request #513 · SaplingLearn/Sapling · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
50c3311
docs(spec): landing page below the hero (#344 phase 2)
AndresL230 Aug 1, 2026
0691218
docs(plan): landing interactive graph, step 1 (#344)
AndresL230 Aug 1, 2026
651de5f
feat(landing): course graph fixtures for the interactive demo (#344)
AndresL230 Aug 1, 2026
f5b89b5
feat(landing): deterministic radial layout + helical entry path (#344)
AndresL230 Aug 1, 2026
d6fc3b9
feat(landing): knowledge graph demo — chips and laid-out render (#344)
AndresL230 Aug 1, 2026
69a718b
feat(landing): helical assembly, parked under reduced motion (#344)
AndresL230 Aug 1, 2026
fed4d12
fix(landing): SSR-safe reduced-motion detection for the graph demo (#…
AndresL230 Aug 1, 2026
fe906f9
test(landing): cold-start the reduced-motion store cache per test (#344)
AndresL230 Aug 1, 2026
1a3c2f5
feat(landing): graph hover blurbs and copy fade on engagement (#344)
AndresL230 Aug 1, 2026
d42d68b
feat(landing): replace HowItWorks and the feature catalog with the gr…
AndresL230 Aug 1, 2026
bb8ca16
test(e2e): journey for the landing knowledge graph (#344)
AndresL230 Aug 1, 2026
1e026e1
fix(landing): viewport-gate the graph assembly, never render it blank…
AndresL230 Aug 1, 2026
5ab1eda
fix(landing): brand-conform the graph section and fit its frame (#344)
AndresL230 Aug 1, 2026
b9e422e
fix(landing): take the outer ring off the horizontal axis (#344)
AndresL230 Aug 1, 2026
5631639
fix(landing): lay the graph out as a radial tree, not concentric ring…
AndresL230 Aug 1, 2026
fc0afe8
fix(landing): grow the graph upward from its base, not down from the …
AndresL230 Aug 2, 2026
5429aa6
fix(landing): tighten the graph's entry sweep to a quarter turn (#344)
AndresL230 Aug 2, 2026
86cfacf
fix(landing): restore atmospheric continuity between the sections (#344)
AndresL230 Aug 2, 2026
c99ed01
feat(landing): three feature bands and a bento of built surfaces (#344)
AndresL230 Aug 2, 2026
7e4ea24
feat(landing): give the knowledge graph product chrome, a dial and a …
AndresL230 Aug 2, 2026
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
62 changes: 62 additions & 0 deletions docs/frontend-testids.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -80,6 +80,10 @@ renders the element.
| Calendar | `calendar` | `frontend/src/components/screens/Calendar.tsx` (the `/calendar` screen — today just the #185 load-failure banner + retry) |
| Gradebook | `gradebook` | `frontend/src/components/screens/Gradebook/Landing.tsx` + `Course.tsx` (the `/gradebook` screens), `frontend/src/components/Gradebook/TranscriptModal.tsx` (the transcript modal), `frontend/src/components/Gradebook/CourseCard.tsx` (the term-aware card links), `frontend/src/components/Gradebook/AssignmentList.tsx` + `AssignmentModal.tsx` (the add-assignment flow the #468 mutation leg drives) — added with the #139 term-switcher/transcript journey |
| Admin analytics | `admin-analytics` | `frontend/src/components/screens/AdminAnalytics.tsx` (the `/admin/analytics` dashboard — range presets/inputs, cost group-by toggle, per-panel retry) — added with the #121 data layer |
| Landing knowledge graph demo | `landing-graph` | `frontend/src/components/marketing/graph/KnowledgeGraphDemo.tsx` (the pre-auth landing page's interactive course-picker + laid-out concept graph, wearing the `landing-surface` chrome with an inspector rail and mastery legend since #344 step 3) |
| Landing feature bands | `landing-band` | `frontend/src/components/marketing/FeatureBand.tsx` (the three full-width bands below the graph; content + side-alternation in `featureBands.tsx`, #344 step 2) |
| Landing surface bento | `landing-bento` | `frontend/src/components/marketing/SurfaceBento.tsx` (the four-tile grid of built product surfaces, #344 step 2) |
| Landing product surfaces | `landing-surface` | `frontend/src/components/marketing/surfaces/*.tsx` (the seven in-page recreations the bands and bento mount, #344 step 2) |

Two surfaces do **not** carry their testids in the screen file named by the
route:
Expand DownExpand Up@@ -324,6 +328,64 @@ Added with the upload → SSE → library journey (#387).
| `library-concepts-toggle-all` | detail panel "Expand all" / "Collapse all" |
| `library-concept-toggle-{idx}` | one concept accordion toggle (render index) |

### `landing-graph`

Added with the #344 landing-page knowledge-graph demo. Ships the static,
fully laid-out render (chips to switch course, nodes/edges for the selected
graph), the helical assembly animation, and hover interaction (detail-panel
swap + copy fade on first engagement); click-to-expand is a later task in the
same spec and will reuse these same testids.

Step 3 wrapped the whole thing in the same `landing-surface` chrome the bands
and bento use — a titled bar over a `--bg-mesh` canvas, an inspector rail and a
mastery legend — so the ids below split into the section's own controls and the
screen's panes.

| testid | element |
| --- | --- |
| `landing-graph` | demo section root (`aria-label` names the selected course) |
| `landing-graph-chip-{courseId}` | one course-picker chip, per `COURSE_GRAPHS` entry — `aria-pressed` marks the active course |
| `landing-graph-copy` | the section's instructional copy block (eyebrow label + heading) — `data-engaged` flips `"true"` on the visitor's first node hover and stays that way across course switches |
| `landing-graph-eyebrow` | the "Your knowledge, mapped" eyebrow label — deliberately *not* faded on engagement (WCAG AA at 0.7rem) |
| `landing-graph-headline` | the "Pick a course. Watch it grow." heading — carries the engagement fade (`ENGAGED_HEADLINE_OPACITY`) |
| `landing-graph-surface` | the product-chrome frame the whole screen sits in (`landing-surface`) |
| `landing-graph-meta` | the chrome bar's right-hand status — `{code} · {conceptCount} concepts · {n}% mastery`, all read off the selected `CourseGraph` |
| `landing-graph-svg` | the `<svg>` itself — its `viewBox` is the desktop or the phone `GraphView` (`graph/layout.ts`), swapped at the mobile breakpoint |
| `landing-graph-node-{nodeId}` | one SVG node group (`<g>`) in the selected course's graph. Its **first `<circle>` is the tier-painted disc at the full node radius** — the phone legibility gate measures that element, so nothing may be drawn in front of it |
| `landing-graph-detail` | the inspector rail's frame. Never empty: with nothing hovered it shows the course (the root node) |
| `landing-graph-detail-name` | the inspected node's name |
| `landing-graph-detail-tier` | its mastery tier as a labelled chip (`Course` for the root, which is an anchor rather than a status) |
| `landing-graph-detail-mastery` | its mastery as a percentage |
| `landing-graph-blurb` | the reserved-height paragraph in the panel that shows the inspected node's one-sentence blurb |
| `landing-graph-legend` | the mastery legend pinned to the foot of the rail |
| `landing-graph-legend-{tier}` | one legend row — `mastered`, `learning`, `struggling` or `unexplored`, each with the tier's own count of drawn concepts |

### `landing-band` / `landing-bento` / `landing-surface`

Added with the #344 step-2 build (feature bands + surface bento). These
surfaces are **static recreations** of shipped product screens — there is
deliberately not a single `<button>`, `<input>` or `<textarea>` among them
(the spec's "no per-tile buttons"), so the lint block below is satisfied
vacuously and stays as a guard on anything added later.

| testid | element |
| --- | --- |
| `landing-band-{bandId}` | one feature band's `<section>` — `bandId` is `upload`, `quiz` or `review` (a stable content id, per the repeated-items rule). Carries `data-surface-side="left"\|"right"`, the resolved alternation |
| `landing-band-{bandId}-eyebrow` | that band's mono micro-label |
| `landing-bento` | the four-tile grid's `<section>` root |
| `landing-bento-eyebrow` | the bento's mono micro-label |
| `landing-surface-upload` | Document Library ingest queue (band 1) |
| `landing-surface-quiz` | an Adaptive Quizzes question mid-answer (band 2) |
| `landing-surface-review` | the Study screen's spaced review + rating trio (band 3) |
| `landing-surface-tutor` | a Tutor exchange (bento) |
| `landing-surface-notes` | a Notetaker note with its linked concepts (bento) |
| `landing-surface-rooms` | a Study Rooms conversation (bento) |
| `landing-surface-gradebook` | a Gradebook course header + assignment rows (bento) |

The `landing-surface-*` ids suffix the *surface* rather than a render index
because each one mounts exactly once and the surface name is the stable
domain id here.

## Enforcement

`frontend/eslint.config.mjs` has a per-file `no-restricted-syntax` block scoped
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); feat(landing): interactive knowledge graph replaces the scroll section (#344 phase 2) by AndresL230 · Pull Request #513 · SaplingLearn/Sapling · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
50c3311
docs(spec): landing page below the hero (#344 phase 2)
AndresL230 Aug 1, 2026
0691218
docs(plan): landing interactive graph, step 1 (#344)
AndresL230 Aug 1, 2026
651de5f
feat(landing): course graph fixtures for the interactive demo (#344)
AndresL230 Aug 1, 2026
f5b89b5
feat(landing): deterministic radial layout + helical entry path (#344)
AndresL230 Aug 1, 2026
d6fc3b9
feat(landing): knowledge graph demo — chips and laid-out render (#344)
AndresL230 Aug 1, 2026
69a718b
feat(landing): helical assembly, parked under reduced motion (#344)
AndresL230 Aug 1, 2026
fed4d12
fix(landing): SSR-safe reduced-motion detection for the graph demo (#…
AndresL230 Aug 1, 2026
fe906f9
test(landing): cold-start the reduced-motion store cache per test (#344)
AndresL230 Aug 1, 2026
1a3c2f5
feat(landing): graph hover blurbs and copy fade on engagement (#344)
AndresL230 Aug 1, 2026
d42d68b
feat(landing): replace HowItWorks and the feature catalog with the gr…
AndresL230 Aug 1, 2026
bb8ca16
test(e2e): journey for the landing knowledge graph (#344)
AndresL230 Aug 1, 2026
1e026e1
fix(landing): viewport-gate the graph assembly, never render it blank…
AndresL230 Aug 1, 2026
5ab1eda
fix(landing): brand-conform the graph section and fit its frame (#344)
AndresL230 Aug 1, 2026
b9e422e
fix(landing): take the outer ring off the horizontal axis (#344)
AndresL230 Aug 1, 2026
5631639
fix(landing): lay the graph out as a radial tree, not concentric ring…
AndresL230 Aug 1, 2026
fc0afe8
fix(landing): grow the graph upward from its base, not down from the …
AndresL230 Aug 2, 2026
5429aa6
fix(landing): tighten the graph's entry sweep to a quarter turn (#344)
AndresL230 Aug 2, 2026
86cfacf
fix(landing): restore atmospheric continuity between the sections (#344)
AndresL230 Aug 2, 2026
c99ed01
feat(landing): three feature bands and a bento of built surfaces (#344)
AndresL230 Aug 2, 2026
7e4ea24
feat(landing): give the knowledge graph product chrome, a dial and a …
AndresL230 Aug 2, 2026
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
62 changes: 62 additions & 0 deletions docs/frontend-testids.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -80,6 +80,10 @@ renders the element.
| Calendar | `calendar` | `frontend/src/components/screens/Calendar.tsx` (the `/calendar` screen — today just the #185 load-failure banner + retry) |
| Gradebook | `gradebook` | `frontend/src/components/screens/Gradebook/Landing.tsx` + `Course.tsx` (the `/gradebook` screens), `frontend/src/components/Gradebook/TranscriptModal.tsx` (the transcript modal), `frontend/src/components/Gradebook/CourseCard.tsx` (the term-aware card links), `frontend/src/components/Gradebook/AssignmentList.tsx` + `AssignmentModal.tsx` (the add-assignment flow the #468 mutation leg drives) — added with the #139 term-switcher/transcript journey |
| Admin analytics | `admin-analytics` | `frontend/src/components/screens/AdminAnalytics.tsx` (the `/admin/analytics` dashboard — range presets/inputs, cost group-by toggle, per-panel retry) — added with the #121 data layer |
| Landing knowledge graph demo | `landing-graph` | `frontend/src/components/marketing/graph/KnowledgeGraphDemo.tsx` (the pre-auth landing page's interactive course-picker + laid-out concept graph, wearing the `landing-surface` chrome with an inspector rail and mastery legend since #344 step 3) |
| Landing feature bands | `landing-band` | `frontend/src/components/marketing/FeatureBand.tsx` (the three full-width bands below the graph; content + side-alternation in `featureBands.tsx`, #344 step 2) |
| Landing surface bento | `landing-bento` | `frontend/src/components/marketing/SurfaceBento.tsx` (the four-tile grid of built product surfaces, #344 step 2) |
| Landing product surfaces | `landing-surface` | `frontend/src/components/marketing/surfaces/*.tsx` (the seven in-page recreations the bands and bento mount, #344 step 2) |

Two surfaces do **not** carry their testids in the screen file named by the
route:
Expand DownExpand Up@@ -324,6 +328,64 @@ Added with the upload → SSE → library journey (#387).
| `library-concepts-toggle-all` | detail panel "Expand all" / "Collapse all" |
| `library-concept-toggle-{idx}` | one concept accordion toggle (render index) |

### `landing-graph`

Added with the #344 landing-page knowledge-graph demo. Ships the static,
fully laid-out render (chips to switch course, nodes/edges for the selected
graph), the helical assembly animation, and hover interaction (detail-panel
swap + copy fade on first engagement); click-to-expand is a later task in the
same spec and will reuse these same testids.

Step 3 wrapped the whole thing in the same `landing-surface` chrome the bands
and bento use — a titled bar over a `--bg-mesh` canvas, an inspector rail and a
mastery legend — so the ids below split into the section's own controls and the
screen's panes.

| testid | element |
| --- | --- |
| `landing-graph` | demo section root (`aria-label` names the selected course) |
| `landing-graph-chip-{courseId}` | one course-picker chip, per `COURSE_GRAPHS` entry — `aria-pressed` marks the active course |
| `landing-graph-copy` | the section's instructional copy block (eyebrow label + heading) — `data-engaged` flips `"true"` on the visitor's first node hover and stays that way across course switches |
| `landing-graph-eyebrow` | the "Your knowledge, mapped" eyebrow label — deliberately *not* faded on engagement (WCAG AA at 0.7rem) |
| `landing-graph-headline` | the "Pick a course. Watch it grow." heading — carries the engagement fade (`ENGAGED_HEADLINE_OPACITY`) |
| `landing-graph-surface` | the product-chrome frame the whole screen sits in (`landing-surface`) |
| `landing-graph-meta` | the chrome bar's right-hand status — `{code} · {conceptCount} concepts · {n}% mastery`, all read off the selected `CourseGraph` |
| `landing-graph-svg` | the `<svg>` itself — its `viewBox` is the desktop or the phone `GraphView` (`graph/layout.ts`), swapped at the mobile breakpoint |
| `landing-graph-node-{nodeId}` | one SVG node group (`<g>`) in the selected course's graph. Its **first `<circle>` is the tier-painted disc at the full node radius** — the phone legibility gate measures that element, so nothing may be drawn in front of it |
| `landing-graph-detail` | the inspector rail's frame. Never empty: with nothing hovered it shows the course (the root node) |
| `landing-graph-detail-name` | the inspected node's name |
| `landing-graph-detail-tier` | its mastery tier as a labelled chip (`Course` for the root, which is an anchor rather than a status) |
| `landing-graph-detail-mastery` | its mastery as a percentage |
| `landing-graph-blurb` | the reserved-height paragraph in the panel that shows the inspected node's one-sentence blurb |
| `landing-graph-legend` | the mastery legend pinned to the foot of the rail |
| `landing-graph-legend-{tier}` | one legend row — `mastered`, `learning`, `struggling` or `unexplored`, each with the tier's own count of drawn concepts |

### `landing-band` / `landing-bento` / `landing-surface`

Added with the #344 step-2 build (feature bands + surface bento). These
surfaces are **static recreations** of shipped product screens — there is
deliberately not a single `<button>`, `<input>` or `<textarea>` among them
(the spec's "no per-tile buttons"), so the lint block below is satisfied
vacuously and stays as a guard on anything added later.

| testid | element |
| --- | --- |
| `landing-band-{bandId}` | one feature band's `<section>` — `bandId` is `upload`, `quiz` or `review` (a stable content id, per the repeated-items rule). Carries `data-surface-side="left"\|"right"`, the resolved alternation |
| `landing-band-{bandId}-eyebrow` | that band's mono micro-label |
| `landing-bento` | the four-tile grid's `<section>` root |
| `landing-bento-eyebrow` | the bento's mono micro-label |
| `landing-surface-upload` | Document Library ingest queue (band 1) |
| `landing-surface-quiz` | an Adaptive Quizzes question mid-answer (band 2) |
| `landing-surface-review` | the Study screen's spaced review + rating trio (band 3) |
| `landing-surface-tutor` | a Tutor exchange (bento) |
| `landing-surface-notes` | a Notetaker note with its linked concepts (bento) |
| `landing-surface-rooms` | a Study Rooms conversation (bento) |
| `landing-surface-gradebook` | a Gradebook course header + assignment rows (bento) |

The `landing-surface-*` ids suffix the *surface* rather than a render index
because each one mounts exactly once and the surface name is the stable
domain id here.

## Enforcement

`frontend/eslint.config.mjs` has a per-file `no-restricted-syntax` block scoped
Expand Down
Loading
Loading