Files
piscine-graph-tweaks/README.md
T
2026-09-12 14:29:44 +02:00

7.4 KiB
Raw Blame History

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:

./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:debuggingLoad 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:
npm install --global web-ext
web-ext sign --channel=unlisted \
  --api-key="$AMO_JWT_ISSUER" --api-secret="$AMO_JWT_SECRET"
  1. 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:configxpinstall.signatures.requiredfalse, 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.