initial commit

This commit is contained in:
2026-09-12 14:29:44 +02:00
commit b65af3aae7
14 changed files with 1746 additions and 0 deletions
+182
View File
@@ -0,0 +1,182 @@
# Piscine Graph Tweaks
Firefox extension for the EPITA piscine exercise graph (`exercises_c`,
`exercises_shell`, …) on `intra.forge.epita.fr`. Two things:
**A progress panel**, bottom right of the page:
- **validated / total** with the percentage and a segmented bar;
- a separate **required** count, which is the one that actually matters;
- breakdown: validated, required left, bonus left, locked.
**A full screen view of the graph**, because the page crams a ~5000px-wide
diagram into a 200px-tall box:
- opens at a readable zoom instead of shrinking everything to fit;
- **search** — dims everything but the matches, and recentres when there is only one;
- **minimap** bottom right, coloured by status; click or drag it to jump anywhere;
- **arrow keys** to pan (Shift for 3×), wheel to zoom, shift+wheel to pan sideways,
drag to pan, double-click to zoom in, click a node to open the exercise;
- `/` search · `Home` readable view · `0` fit everything · `+` / `` zoom · `Esc` close.
No network request is made: everything is read from the page itself.
## How it reads the graph
The graph is a mermaid `stateDiagram` rendered as inline SVG, and each node keeps
its whole state in its `id`:
```
state-"_required=true/_validated=true/_accessible=true/…/exercises_c/clang~format"-0
```
So the three booleans are parsed straight from the id — no color guessing — and
`~` is mermaid's escape for `-` in the path. Status mapping:
| `_validated` | `_accessible` | `_required` | status |
|--------------|---------------|-------------|---------------|
| `true` | – | | validated |
| `false` | `false` | | locked |
| `false` | `true` | `true` | required left |
| `false` | `true` | `false` | bonus left |
Verified against the live page: 91 nodes parsed, the only skipped element being
mermaid's `state-root_start-1` marker.
## Why there is no "started but unfinished" state
There is none in the page. Each node carries exactly those three booleans, and
`_validated` only flips once everything passes. An exercise started this morning
and one never opened produce **byte-for-byte identical markup** — compare
`gdb_wristwatch` and `test_a_bit`, both `_required=true/_validated=false/
_accessible=true`. So per-exercise completion cannot come from this page, and the
extension does not pretend otherwise.
If you ever want real "started" detection, it has to come from the GitLab API
rather than the graph — one call listing the exercise projects and their
`last_activity_at` would say which ones you have pushed to:
```
/api/v4/groups/<url-encoded group path>/projects?per_page=100&order_by=last_activity_at
```
That is one request for the whole graph, not one per exercise. Not wired in: it
needs checking against the real API response first.
## How the full screen view works
It **moves** the real `<svg>` into the overlay rather than cloning it (a
placeholder holds its spot in the page), so mermaid's own `<style>` block — which
lives inside the SVG and is scoped to its id — keeps working untouched. It then
takes over the `svg-pan-zoom` viewport `<g>` transform instead of fighting that
instance. Inherited CSS custom properties (`--required-validated` and friends) do
not survive the move, so they are resolved and re-declared on the overlay first.
Closing puts every attribute back exactly as it was.
---
## Installing (for you, while developing)
1. Open `about:debugging#/runtime/this-firefox`
2. **Load Temporary Add-on…**
3. Pick this folder's `manifest.json`
4. Open the graph page — the panel shows up bottom right
"Temporary" means it **disappears when Firefox restarts**. That is the
development mode: after each code change, hit **Reload** on `about:debugging`.
For a permanent install, use the same route as your friends below.
## Sharing it with friends (without publishing to the store)
Build the package:
```sh
./build.sh # -> dist/piscine-graph-tweaks-<version>.zip and .xpi
```
Then pick one of three routes. The catch: **Firefox release refuses to install an
unsigned extension permanently**, so a `.xpi` dropped in a Discord channel will
not install by double-click.
### Option A — the zip plus temporary loading (simplest, zero setup)
Send the `.zip`, they unpack it, then `about:debugging` → *Load Temporary
Add-on…* → `manifest.json`.
- works on any Firefox, right away
- has to be redone every time the browser restarts
### Option B — "unlisted" signing on AMO (best for daily use)
Mozilla signs the extension **without publishing it** in the catalogue: it stays
unlisted, you distribute the `.xpi` yourself, and it installs permanently on a
normal Firefox.
1. Create an account on https://addons.mozilla.org, then generate an API key at
https://addons.mozilla.org/developers/addon/api/key/
2. Sign it:
```sh
npm install --global web-ext
web-ext sign --channel=unlisted \
--api-key="$AMO_JWT_ISSUER" --api-secret="$AMO_JWT_SECRET"
```
3. You get a **signed** `.xpi` in `web-ext-artifacts/`. Friends open it with
Firefox (drag into the window, or `Ctrl+O`) → *Add*.
The extension id is already pinned in `manifest.json`
(`piscine-graph-tweaks@valentin`) — required for signing, and it must not change
after the first signature.
### Option C — Firefox Developer Edition / Nightly / ESR
On those builds only, signature enforcement can be turned off: `about:config`
`xpinstall.signatures.required``false`, then install the unsigned `.xpi`.
Pointless on Firefox release, where the pref is ignored.
---
## Layout
```
manifest.json extension manifest (MV3) and the matched URLs
src/content.js node parsing, stats, the progress panel
src/viewer.js full screen view: pan/zoom, search, minimap
src/panel.css every injected style
src/inject.js injects the network hook into the page world
src/net-hook.js records what the page fetches (discovery aid)
src/icon.svg icon
tools/dump-graph.js console snippet: parsed nodes, works without the extension
tools/make-fixture.js console snippet: captures the graph for offline testing
tests/build-harness.js rebuilds tests/harness.html from a fixture + current src
tests/shot.sh screenshots the harness with headless Firefox
build.sh produces dist/*.zip and dist/*.xpi
```
`src/viewer.js` must load **before** `src/content.js` — the panel's button calls
`window.__pgtViewer`.
## Testing
Two ways, both offline-friendly:
- **Against a captured page.** Run `tools/make-fixture.js` in the console on the
graph page, save it with `wl-paste > tests/fixture.json`, then
`node tests/build-harness.js && tests/shot.sh`. The harness inlines the current
`src/` so any edit is one command away from a screenshot.
- **Against the live page.** Anything driving a real browser works; the parser and
both screenshots in this repo's history were checked that way.
## Other projects
The manifest already matches `*/root/exercises_*` on `intra.forge.epita.fr`, so
`exercises_shell` and any future `exercises_python` work with no change. The panel
title is derived from the project segment of the node ids.
## If the numbers look wrong
The graph generator would have to have changed. Open the console on the page and
run `__pgtDump()`: it prints the parsed nodes and the raw HTML of the first one,
which is what `RE_STATE_ID` in `src/content.js` needs to match. `__pgtNet()` lists
what the page fetched, in case richer data ever appears.