7.4 KiB
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 ·Homereadable view ·0fit everything ·+/−zoom ·Escclose.
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)
- Open
about:debugging#/runtime/this-firefox - Load Temporary Add-on…
- Pick this folder's
manifest.json - 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:
./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.
- Create an account on https://addons.mozilla.org, then generate an API key at https://addons.mozilla.org/developers/addon/api/key/
- Sign it:
npm install --global web-ext
web-ext sign --channel=unlisted \
--api-key="$AMO_JWT_ISSUER" --api-secret="$AMO_JWT_SECRET"
- You get a signed
.xpiinweb-ext-artifacts/. Friends open it with Firefox (drag into the window, orCtrl+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.jsin the console on the graph page, save it withwl-paste > tests/fixture.json, thennode tests/build-harness.js && tests/shot.sh. The harness inlines the currentsrc/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.