feat: multi-volume, navbar redesign, PyRunner v1#1
Conversation
Adds three new plugin-content-docs instances (Manuale del Programmatore, dell'Artefice, dell'Archivista) alongside the existing default docs, each with its own content folder under docs/<volume>/, its own routeBasePath, and a sidebars/<volume>.ts that exports one sidebar per "percorso" (IT / Liceo / ITS). Sidebars currently reference only the placeholder intro.mdx for each volume — real chapters come once we have UI to switch percorso. PathContext (src/contexts/PathContext.tsx) persists per-volume the user's chosen path in localStorage under pdb:path:<volumeId>, with cross-tab sync via the storage event. The provider is wired through a Root swizzle so any component (future navbar selector, sidebar resolver, off-path banner) can read or update it. Each volume has the same three placeholder paths for now; per- volume customization happens when we wire the navbar selector. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PathSelector (src/components/PathSelector) reads the active plugin-content-docs instance via useActivePlugin, fetches that instance's full sidebar set via useAllDocsData (each sidebar key = a percorso: it/liceo/its by current convention), and renders a pill-shaped segmented control in the navbar center. Clicking writes to PathContext + localStorage; the component hides itself on non-volume routes (so the navbar center stays empty on the homepage and elsewhere). To slot it into the navbar I swizzled Navbar/Content (otherwise the only public extension point is themeConfig.navbar.items, which can't render arbitrary React). The original layout had left/right slots; I added a center slot between them. The actual sidebar swap happens in a DocRoot swizzle: useResolvedSidebar reads useDocsVersion().docsSidebars (the full set for the current volume) and replaces sidebarName/sidebarItems in DocsSidebarProvider with the user's pick when one exists and is valid. Falls back to the default Docusaurus selection for any volume not in the registered set and for users who haven't picked a path yet. Verified end-to-end with a second placeholder doc (volumes/programmatore/variabili.mdx) listed under it+liceo but not under its: switching the selector now visibly adds/removes the lesson from the sidebar without a reload, and the choice persists across refreshes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When the user opens a lesson whose docId isn't present in their chosen path's sidebar (e.g. lands on /programmatore/variabili while on ITS, but variabili lives only in IT+Liceo), an inline banner appears above the chapter kicker. The banner lists the *other* paths in the same volume that DO contain the lesson; each is rendered as an inline button that calls setPath(volumeId, otherPath). Switching is in-place — no navigation — so the URL stays the same, the sidebar reshuffles to include the lesson, and the banner self-removes. Detection lives in useOffPathInfo (src/components/OffPathBanner) which walks each sidebar's items recursively for a docId match (categories included). Returns null when: - the active plugin isn't one of the three volumes - the user has never picked a path (we don't surprise them) - the lesson IS in the active path's sidebar Path labels (Istituto Tecnico / Liceo / ITS) extracted to src/contexts/pathLabels.ts and reused by both PathSelector (short labels for the navbar pill) and the banner (full labels for prose). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…nment fix Banner collapsed from a two-paragraph stack (~120px tall, lots of empty space) into a single horizontal line (~46px). Title and detail are now one continuous sentence: ⓘ Lezione fuori dal percorso [ITS] · Disponibile in [IT] · [Liceo] - Replaced the inline SVG with FontAwesome circle-info (consistent with our `<FAIcon>`/`<FontAwesomeIcon>` convention) - "passa a X" prefix dropped — under "Disponibile in:" the bare path name is enough and reads better - Capitalized "Disponibile" and "Non inclusa" for sentence-case consistency after the · separator - align-items: center on the flex container, but the message <p> wasn't centering because .theme-doc-markdown p (specificity 0,1,1) was adding margin-bottom 1.15em that inflated the banner content area and anchored the <p> to the top of the line. Bumped specificity with .banner > .message (0,2,0) to wipe the margin. Now icon-cy = msg-cy = banner-cy. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the single "Libro" entry with a dropdown listing the three volumes (Programmatore, Artefice, Archivista) plus a "Versione precedente" entry pointing at the legacy /docs/intro content until we migrate it into Programmatore. Each volume entry uses type: 'doc' with docsPluginId so Docusaurus highlights the right item when the user is inside that plugin instance. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… grid Adds a fourth plugin-content-docs instance (apprendista) for exercises and lab projects, alongside the three theory volumes. Bookkeeping spread across: VolumeId union in PathContext, the three VOLUMES sets in PathSelector / OffPathBanner / DocRoot, sidebars/apprendista.ts placeholder, navbar "Libri" dropdown. Homepage hero cards go from 2 to 4 in a natural 2x2 grid (existing CSS already used grid-template-columns: 1fr 1fr): Programmatore (blue), Artefice (pink), Archivista (amber), Apprendista (green). Added two new VolumeCard accent palettes (amber + green) plus two inline SVG icons (database, flask). Card targets switch from legacy /docs/ URLs to the new volume roots. Naming note: "Biblioteca" not "Manuale" because the fourth volume is a curated collection of practice material, not a linear didactic narrative like the other three. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Title: "Python doesn't byte" rendered as two spans via a Navbar/Logo swizzle. "Python" is fg-strong, "doesn't byte" is muted italic in Crimson Pro, matching the design's navbar treatment (which inspired the homepage hero word split). Icons: both .github-link and .sponsorship-link now use a mask-image + background-color: currentColor pattern instead of hardcoded SVG fills. This means the icon color follows the link color (and any :hover/light/dark variant) without needing a separate SVG per state. GitHub previously turned green on hover because the hover SVG had a baked-in #25c2a0 fill; now both icons fade to var(--at-accent) on hover via a plain CSS color transition. Replaced the heart icon (Support sponsorship link) with a coffee-mug glyph (Lucide style) and renamed the navbar entry "Support" → "Offrimi un caffè". Removed the Rainbow Bits navbar entry and the unused .rainbowbits-link + .designedBy/.heart CSS leftovers from the Docusaurus template. Removed the "Versione precedente" entry from the Libri dropdown — the legacy /docs/ content stays reachable by direct URL and via the footer until we migrate it into Programmatore. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
GitHub and "Offrimi un caffè" navbar links rebuilt to match the
ColorModeToggle visually: 2rem round buttons with just the icon
glyph, label collapsed via font-size: 0 (kept in DOM + aria-label
for SR). Hover paints a soft bg and tints the icon accent.
On hover/focus the link's ::after expands into a neon popup
tooltip with the human label — the same Lumos/Nox treatment:
GitHub → cyan (matches our default accent)
Offrimi un caffè → warm amber/brown (#c8845c border,
#f0b072 text, brown box-shadow)
Specificity quirk: .navbar__link sits later in the file and was
overriding .github-link's font-size: 0. Bumped to
.navbar__link.github-link (0,2,0) so the icon-only collapse
actually wins.
Added aria-label to the sponsorship navbar item too (GitHub
already had one) so screen readers still announce both even with
visible text hidden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
GitHub and "Offrimi un caffè" rebuilt as a shared React component (NavbarIconButton) instead of CSS-only ::before/::after on config-driven navbar items. This gives them the same DOM shape as ColorModeToggle — round 2rem button + FontAwesomeIcon + a proper tooltip <span> with two pseudo-element carets (border color + bg color stacked) pointing at the icon. Tooltips now have the same upward caret as Lumos/Nox; the previous CSS-only ::after approach couldn't draw an arrow outside its own box without ugly tricks. Two accent variants: cyan (`--at-accent` family) for GitHub, amber/brown for the coffee button (#c8845c border, #f0b072 text, warm box-shadow). ColorModeToggle switched from Docusaurus' IconLightMode / IconDarkMode / IconSystemColorMode to FontAwesome sun / moon / circle-half-stroke at 18px — same family + sizing as the new buttons, so all four icons in the navbar right rail look unified. mug-hot is rendered at 20px because the steam puffs push it visually smaller otherwise. FontAwesome library.add(fab, fas) moved from MDXComponents.tsx (which is only loaded on MDX-rendered routes) to Root.tsx so non-MDX pages like the homepage get the icons registered. GitHub and sponsorship links removed from themeConfig.navbar.items — they're rendered directly by the Navbar/Content swizzle now. Old per-class CSS in custom.css (mask SVGs + ::after tooltips) deleted. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three SVGs (starfighter, user-sith, mug-saucer) copied from static/img/icons/ into src/icons/ so Docusaurus' @svgr/webpack imports them as React components with currentColor support. Static copies stay in place for any future <img src=...> use. ColorModeToggle: FA sun/moon swapped for Sith (shown in light mode, click → dark) / Starfighter (shown in dark mode, click → light). The icon represents the *destination* of the click, not the current state — same affordance as the tooltip text. Lumos → "Light side" (cyan), Nox → "Dark side" (red lightsaber palette replaces the old magenta). Hover color on the toggle now follows data-next-mode: cyan when next is light, red when next is dark, matching the tooltip palette. NavbarIconButton: `icon` prop relaxed from FA IconProp to ReactNode so callers can pass either a FontAwesomeIcon or an imported SVG component. Coffee button switched to mug-saucer (Pro SVG); GitHub keeps the FA brand glyph. Dropped the iconSize prop — all icons rendered at a uniform 20×20 via CSS on .icon and .icon > svg. Added a 'red' accent variant. Default icon color is var(--at-muted) on all four buttons; hover paints it with the variant's accent (cyan / amber / red). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sostituisce gradualmente docusaurus-live-brython con un PyRunner custom basato
su Brython + CodeMirror 6, integrato nei 4 plugin docs (default + 3 volumi) via
remark che converte i fence ```py live``` in <PyRunner code=...>.
Componenti principali:
- plugins/pyrunner: walk dei .py in static/py-examples/, setGlobalData, watch,
hook injectHtmlTags gated da skipScriptInjection (true in PR1 per convivere
con docusaurus-live-brython, da rimuovere in PR2).
- plugins/pyrunner/remark.js: trasforma py live in JSX, parsing meta booleani
e key=value (readonly, title, maxLines).
- src/pyBoot.ts: client module idempotente che inizializza __BRYTHON__.
- src/theme/PyRunner: Editor (CodeMirror 6, theme Atmospheric), Output con
stdout/stderr separati, Toolbar con run/reset/copy/traffic lights stile macOS,
bridge eventi bry_notify ↔ React.
- docs/pyrunner-test.mdx (unlisted): sandbox con 7 casi di test.
Pol di UX al primo passaggio:
- copy button con animazione check (replica del CodeBlock Docusaurus, riusa
@theme/Icon/Copy + @theme/Icon/Success per coerenza visiva).
- font code ridotto a 0.85rem (assoluto) → più piccolo del testo normale.
- font output alzato a 0.92rem per leggibilità.
- placeholder "Esegui" sostituito con chip che riprende i token del pulsante run.
Bug fix scoperti testando in browser:
- bryBridge: id del div di output allineato a Config.OUTPUT_DIV
(${codeId}_brython_result, non ${codeId}_output) — sbloccava ogni esecuzione.
- bryBridge: \n di sicurezza prima della chiusura del triple-quote del wrapper,
altrimenti codice che termina con `"` produceva SyntaxError.
- Editor: per il flag readonly servono EditorState.readOnly + EditorView.editable
insieme — il primo da solo non disabilita contenteditable.
- Output: setStatus('error') al primo stderr (in Brython lo stderr è proxy
affidabile dell'eccezione).
- styles: .outputBody non più flex column per evitare doppia spaziatura dopo
i \n di print, header allineato con line-height: 1 + align-self center.
Codemirror 6 aggiunto come dipendenza diretta (commands, lang-python, language,
state, view). docusaurus-live-brython resta in themes finché non parte PR2.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tre nuove feature del PyRunner (step 10-11 della roadmap §13 + nuova pagina
playground), tutte sotto BrowserOnly e senza dipendenze extra runtime.
Fullscreen condizionale
- ResizeObserver sul wrapper editor (+ .cm-scroller interno) → state
`overflowing` aggiornato a ogni resize/cambio codice.
- Bottone faExpand in toolbar visibile solo se overflowing (rispetta la
decisione §14 punto 7: no opt-in esplicito, solo quando serve).
- onClick apre /playground in nuovo tab. Se l'utente non ha editato e c'è
un `src`, l'URL usa `?src=` (corto, riusa globalData del plugin);
altrimenti `?code=<base64url>` con encoding UTF-8-safe via TextEncoder
(gestisce accentate italiane). `?title=` + `?p=` (prompt) opzionali.
Pagina /playground
- src/pages/playground.tsx + .module.css: PyRunner embedded a
calc(100vh - navbar), noFooter.
- Lazy useState init che decodifica i query params (sotto BrowserOnly,
quindi window è disponibile). Empty state per visita diretta senza
parametri.
- Nuova prop `embedded` del PyRunner: rimuove margin/border-radius,
flex column full-height, max-height dell'output spostato a 38vh.
In embedded mode il bottone fullscreen è sempre nascosto.
"Spiegamelo facile"
- Nuovo modulo share.ts con encode/decode base64url + DEFAULT_EXPLAIN_PROMPT
+ buildExplainText (placeholder `{code}`, `{contextTitle}`).
- Bottone faWandMagicSparkles in toolbar (accent-colored), visibile per
default, nascosto con prop `noExplain`.
- Click → copia in clipboard via navigator.clipboard con fallback
copy-text-to-clipboard. Toast non-bloccante (2s, animazione pyrToast)
in alto a destra del runner.
- `{contextTitle}` da document.title strippato del suffisso "| <siteTitle>"
che Docusaurus aggiunge automaticamente.
- Prop `explainPrompt` per template custom per-lezione (es. cicli for,
list comprehension): override completo con stessi placeholder.
Sanitizzazione hooks
- PR1 aveva 17 occorrenze di hooks-after-conditional-return
(react-hooks/rules-of-hooks): spostata l'early return su !data sotto
tutti gli hook, accessi via `data?.libUrl ?? ''` ecc. Niente comportamento
cambiato, lint pulito sui file toccati.
Sandbox
- docs/pyrunner-test.mdx: 4 nuovi casi (§8-11): default explain, custom
explainPrompt, noExplain, fullscreen forzato con maxLines=3.
Verifica in Chrome
- /playground apre, decodifica `?code=`, esegue codice live, rende empty
state senza params.
- Fullscreen visibile solo dove c'è overflow (§7, §11); assente altrove.
- Clipboard intercettata con writeText hook: contiene il template con
`{code}` e `{contextTitle}` risolti. Override custom (§9) funzionante.
- `noExplain` (§10) nasconde il bottone wand come atteso.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…(PR3)
Cut-over completo dal plugin upstream al PyRunner custom. Chiude la roadmap
§13 step 12-14 del piano in .claude/PLAN-pyrunner.md.
Migrazione fence
- 14 occorrenze ```py live_py``` → ```py live``` in:
docs/basi-del-linguaggio/{le-variabili, le-stringhe, input, output}.mdx
(sed in-place, allineato al meta nuovo che il remark di pyrunner riconosce).
Rimozione docusaurus-live-brython
- docusaurus.config.ts: rimosso `themes: ['docusaurus-live-brython']`. Il
plugin pyrunner ora inietta da solo gli script Brython (`brython.min.js`
+ `brython_stdlib.js` da jsdelivr CDN) tramite il suo `injectHtmlTags`.
- Rimossa l'opzione `skipScriptInjection: true` dal config: era un
workaround di convivenza per PR1, ora il default (false) è quello giusto.
Aggiornato il commento in plugins/pyrunner/index.js per riflettere il
nuovo ruolo generale (passare a true solo se un altro plugin inietta
già gli script, niente più riferimenti a PR2/upstream).
- `npm uninstall docusaurus-live-brython` → -2 deps dirette + 60+ deps
transitive (incluso il bundle nested di @docusaurus/* 3.4.0 che era il
motivo principale dietro la decisione di sostituire — ora il dedupe è
pulito).
Verifica
- npm run build: OK (server 4.6s, client 8.7s).
- /docs/basi-del-linguaggio/le-stringhe (7 fence migrati): tutti i 7
PyRunner renderizzano, Brython scripts iniettati una sola volta,
esecuzione del primo runner "Completato in 650 ms" output corretto.
CLAUDE.md + ROADMAP
- CLAUDE.md: aggiornata sezione dipendenze (PyRunner custom + CodeMirror 6
+ copy-text-to-clipboard al posto di docusaurus-live-brython), directory
structure (plugins/pyrunner/, src/pages/playground.tsx, src/pyBoot.ts,
static/py-examples/), config highlights (plugin pyrunner + 4 docs-content),
conventions MDX (sintassi ```py live``` con i nuovi meta), security audit
rivista 2026-05-27 (41→41 advisories, ma niente più copie nested di
@docusaurus/* da fork upstream).
- .claude/ROADMAP.md: PR1/PR2/PR3 marcate completate con ref commit
(80d57fe, bfd87ab, current); cancellato il TODO "verificare
compatibilità con docusaurus-live-brython" (N/A ora).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- statusDot allineato al centro ottico delle uppercase (top: -1px) - font-size editor 0.85rem → 1.05rem; --ifm-code-font-size 95% → 80% (entrambi convergono a ~16.8px) - palette --py-* spostata in custom.css come globale, allineata a Prism Github (light) e Dracula (dark); rimossi gli override locali su .runner - commenti in CodeMirror ora in Monaspace Radon (come nei code-block) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- SRI sha384 sui bundle Brython da jsdelivr (CDN compromise/MITM backstop) - Banner di warning su /playground?code= per codice da link non fidati - Cap su output (1000 righe / 256 KB) contro DoS lato client da loop infiniti - Rimosso ?p= da /playground per evitare clipboard-poisoning del prompt "Spiegamelo facile" via URL condivisa - Assert difensivo sul formato di codeId prima dell'interpolazione nel template Python eseguito via __BRYTHON__.runPythonSource - Rename sanitizePyScript → escapeForTripleQuote con commento che chiarisce che non è un security boundary (l'exec è by design) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Large bundle that merges three parallel workstreams onto a single branch: a multi-volume docs infrastructure (4 plugin-content-docs instances with per-path sidebars and a persisted PathContext), a navbar redesign (shared NavbarIconButton, two-tone logo via Navbar/Logo swizzle, Star Wars-themed color mode toggle), and a custom PyRunner v1 component (Brython 3.12 + CodeMirror 6) that replaces docusaurus-live-brython, plus a local Docusaurus plugin, a /playground page, and migrations of existing py live_py fences to py live.
Changes:
- New multi-volume docs (
volumes/<vol>/,sidebars/<vol>.ts),PathContext+PathSelector+OffPathBanner+DocRootswizzle. - Navbar redesign:
NavbarIconButton, swizzles forNavbar/Logo,Navbar/Content,ColorModeToggle; new SVG icons; large CSS cleanup incustom.css. - PyRunner stack:
plugins/pyrunner/(loader + remark + script injection with SRI),src/theme/PyRunner/(Editor, Toolbar, Output, bryBridge, share),src/pyBoot.ts,/playgroundpage; cut-over of 14live_py→livefences and removal ofdocusaurus-live-brythondep.
Reviewed changes
Copilot reviewed 53 out of 61 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| volumes/{programmatore,artefice,archivista,apprendista}/*.mdx | Placeholder intro/lessons for new volumes |
| sidebars/{programmatore,artefice,archivista,apprendista}.ts | Per-path sidebars (it/liceo/its) per volume |
| src/contexts/PathContext.tsx, pathLabels.ts | localStorage-backed selected-path state, cross-tab sync |
| src/components/PathSelector/* | Segmented control shown only inside a volume |
| src/components/OffPathBanner/* | Notice when current doc is not in the selected path |
| src/components/VolumeCard/index.tsx | Added amber/green accents for new volume cards |
| src/components/NavbarIconButton/* | Shared icon button with neon tooltip (cyan/amber/red) |
| src/theme/Root.tsx | Registers FA library + injects PathProvider |
| src/theme/Navbar/Logo/, Navbar/Content/, ColorModeToggle/* | Swizzles for two-tone title, PathSelector slot, icon buttons, Sith/Starfighter icons |
| src/theme/DocRoot/index.tsx | Swaps sidebar at render time based on PathContext |
| src/theme/DocItem/Content/index.tsx | Renders <OffPathBanner/> at top of doc content |
| src/theme/MDXComponents.tsx | Registers PyRunner as MDX global |
| src/theme/PyRunner/* | Custom Brython + CodeMirror 6 runner (Editor, Toolbar, Output, bryBridge, share, types, styles) |
| src/pyBoot.ts | One-shot Brython bootstrap (idempotent, timeout) |
| src/pages/playground.tsx + .module.css | Fullscreen playground page with untrusted-URL warning |
| src/pages/index.tsx | Home now has 2x2 volume grid |
| src/css/custom.css | Removed legacy header-icon CSS; added Prism-matched --py-* palette |
| src/icons/.svg, static/img/icons/.svg | New Sith/Starfighter/mug-saucer/code-branch/github icons |
| static/py-examples/_test/{hello,somma}.py | Test fixtures for PyRunner src= and ### PRE/POST |
| plugins/pyrunner/{index.js,remark.js,package.json} | Local Docusaurus plugin: walks .py examples, injects Brython scripts with SRI, transforms ```py live into <PyRunner> |
| docusaurus.config.ts | Registers 4 docs plugin instances + pyrunner; navbar dropdown for "Libri"; drops themes: ['docusaurus-live-brython'] |
| package.json | Adds CodeMirror 6 deps; removes docusaurus-live-brython |
| docs/pyrunner-test.mdx | Unlisted sandbox page for PyRunner verification |
| docs/basi-del-linguaggio/{output,le-variabili,le-stringhe,input}.mdx | Migrate py live_py → py live fences |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
Bundle ampio che chiude tre filoni di lavoro paralleli sviluppati sullo stesso branch. Sono separati nei commit (ben distinti per topic) ma condivisi in un unico merge per evitare cherry-pick complessi tra branch che si toccano.
1. Infrastruttura multi-volume
Quattro istanze
plugin-content-docs(Programmatore, Artefice, Archivista, Apprendista) con contenuti involumes/<volume>/, sidebar dedicata per percorso (IT / Liceo / ITS),PathContextpersistente inlocalStorage, runtime sidebar swap via swizzleDocRoot, banner inline per lezioni fuori-percorso, dropdown "Libri" in navbar, 2x2 grid in home.Commit chiave:
2fb5cb6,7e3a4a0,c0403bf,f725e0d,8667e68,1bf2800.2. Redesign navbar
Refactoring dei link icon-only (GitHub, coffee) in componente
NavbarIconButtoncondiviso conColorModeToggle. Tooltip neon con caret, due varianti accent (cyan / amber). Titolo two-tone "Python doesn't byte" via swizzleNavbar/Logo. Iconografia Star Wars per il color mode toggle (Sith / Starfighter) con hover color per-button.Commit chiave:
30cb06e,6de090e,9847b67,24de645.3. PyRunner v1 (sostituisce
docusaurus-live-brython)Componente custom Brython 3.12 + CodeMirror 6 per esecuzione Python in-pagina. Plugin Docusaurus locale (
plugins/pyrunner/) con walk distatic/py-examples/, remark che converte```py livein<PyRunner code=...>, injection script Brython viainjectHtmlTags. Toolbar con traffic lights stile macOS, fullscreen condizionale (visibile solo su overflow), pagina/playground, feature "Spiegamelo facile" che copia un prompt LLM in clipboard. Mitigazioni di sicurezza post-audit: SRI sui bundle CDN, cap output (1000 righe / 256 KB), banner warning su/playground?code=, rimozione?p=per evitare clipboard-poisoning.Cut-over completo:
docusaurus-live-brythonrimosso, 14 fence migrati dapy live_pyapy live ```. -60+ deps transitive, pulizia del dedupe@docusaurus/*3.4.x nested.Commit chiave:
80d57fe(PR1 scaffold),bfd87ab(PR2 fullscreen/playground/explain),5440941(PR3 cut-over),749d284(style polish),3f57aab(security).Test plan
test-deploy.yml)rainbowbits.cloud/python-doesnt-byte/:/programmatore/variabilicon percorso ITS attivopy live ```` (es.basi-del-linguaggio/le-stringhe): tutti i PyRunner si caricano, esecuzione OK/playgroundaccessibile da fullscreen di un PyRunner overflowing