From 48f3fb738bae629bbb57d042e1911e8a47e79e4a Mon Sep 17 00:00:00 2001 From: user Date: Tue, 21 Jul 2026 18:58:00 +0200 Subject: [PATCH] feat(L02): add FPGA bring-up and programming card --- .gitea/workflows/render-pdf.yml | 63 + .gitignore | 30 + Makefile | 19 + README.md | 28 + answers/fpga-bringup-evidence.md | 17 + board/board-contract.yaml | 11 + doc/assets/bringup-gate.png | Bin 0 -> 123773 bytes doc/assets/bringup-gate.svg | 13 + doc/generated/main.tex | 356 ++++ doc/main.tex | 7 + doc/pdf/.gitkeep | 0 json/card_source.json | 22 + lesson-flow.yaml | 28 + package-lock.json | 811 ++++++++ package.json | 16 + scripts/card_dev.mjs | 75 + scripts/card_server.mjs | 918 ++++++++ scripts/export_lesson_report.mjs | 107 + scripts/lib/card_control.mjs | 376 ++++ scripts/lib/card_progress.mjs | 193 ++ scripts/lib/card_state_db.mjs | 161 ++ scripts/lib/checkpoint_controller.mjs | 423 ++++ scripts/lib/nvim_ui_bridge.mjs | 447 ++++ scripts/render_card_layouts.sh | 20 + scripts/render_pdf.sh | 85 + src/tasks/task01_pin_polarity.sv | 3 + src/tasks/task02_bringup_blinker.sv | 7 + src/tasks/task03_programming_gate.sv | 13 + stem-card.yaml | 10 + tests/card_contract.test.mjs | 5 + tests/card_state_db.test.mjs | 67 + tests/reference_model.py | 29 + tests/task01_pin_polarity_tb.sv | 4 + tests/task02_bringup_blinker_tb.sv | 4 + tests/task03_programming_gate_tb.sv | 5 + tests/test_rtl.sh | 10 + tests/validate_board_contract.py | 9 + tools/card-action.sh | 14 + web/app.css | 1 + web/app.js | 61 + web/card-data.json | 2764 +++++++++++++++++++++++++ web/figures/bringup-gate.svg | 13 + web/index.html | 15 + web/style.css | 42 + 44 files changed, 7302 insertions(+) create mode 100644 .gitea/workflows/render-pdf.yml create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 answers/fpga-bringup-evidence.md create mode 100644 board/board-contract.yaml create mode 100644 doc/assets/bringup-gate.png create mode 100644 doc/assets/bringup-gate.svg create mode 100644 doc/generated/main.tex create mode 100644 doc/main.tex create mode 100644 doc/pdf/.gitkeep create mode 100644 json/card_source.json create mode 100644 lesson-flow.yaml create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scripts/card_dev.mjs create mode 100644 scripts/card_server.mjs create mode 100644 scripts/export_lesson_report.mjs create mode 100644 scripts/lib/card_control.mjs create mode 100644 scripts/lib/card_progress.mjs create mode 100644 scripts/lib/card_state_db.mjs create mode 100644 scripts/lib/checkpoint_controller.mjs create mode 100644 scripts/lib/nvim_ui_bridge.mjs create mode 100755 scripts/render_card_layouts.sh create mode 100755 scripts/render_pdf.sh create mode 100644 src/tasks/task01_pin_polarity.sv create mode 100644 src/tasks/task02_bringup_blinker.sv create mode 100644 src/tasks/task03_programming_gate.sv create mode 100644 stem-card.yaml create mode 100644 tests/card_contract.test.mjs create mode 100644 tests/card_state_db.test.mjs create mode 100755 tests/reference_model.py create mode 100644 tests/task01_pin_polarity_tb.sv create mode 100644 tests/task02_bringup_blinker_tb.sv create mode 100644 tests/task03_programming_gate_tb.sv create mode 100755 tests/test_rtl.sh create mode 100755 tests/validate_board_contract.py create mode 100755 tools/card-action.sh create mode 100644 web/app.css create mode 100644 web/app.js create mode 100644 web/card-data.json create mode 100644 web/figures/bringup-gate.svg create mode 100644 web/index.html create mode 100644 web/style.css diff --git a/.gitea/workflows/render-pdf.yml b/.gitea/workflows/render-pdf.yml new file mode 100644 index 0000000..2362972 --- /dev/null +++ b/.gitea/workflows/render-pdf.yml @@ -0,0 +1,63 @@ +name: Render PDF + +on: + push: + branches: + - deploy + workflow_dispatch: + +jobs: + render-pdf: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install TeX + run: | + if ! command -v latexmk >/dev/null 2>&1; then + if ! command -v apt-get >/dev/null 2>&1; then + echo "latexmk is missing and apt-get is unavailable" >&2 + exit 1 + fi + if command -v sudo >/dev/null 2>&1; then + SUDO=sudo + else + SUDO= + fi + $SUDO apt-get update + $SUDO apt-get install -y --no-install-recommends \ + latexmk \ + texlive-fonts-recommended \ + texlive-lang-polish \ + texlive-latex-base \ + texlive-latex-extra \ + texlive-latex-recommended + fi + + - name: Render PDF with commit metadata + run: ./scripts/render_pdf.sh + + - name: Commit rendered PDF + run: | + set -eu + repo_name="${GITHUB_REPOSITORY#*/}" + pdf_file="$(find doc/pdf -maxdepth 1 -type f -name '*.pdf' -print -quit)" + if [ -z "$pdf_file" ]; then + echo "rendered PDF is missing" >&2 + exit 1 + fi + remote_sha="$(git ls-remote origin refs/heads/deploy | cut -f1)" + if [ "$remote_sha" != "$GITHUB_SHA" ]; then + echo "deploy advanced from $GITHUB_SHA to $remote_sha; skip publishing stale PDF" + exit 0 + fi + git config user.name "gitea-actions" + git config user.email "gitea-actions@noreply.local" + printf '%s\n' "$GITHUB_SHA" > doc/pdf/source-commit.txt + git add -u doc/pdf + git add -f "$pdf_file" + git add doc/pdf/source-commit.txt + git diff --cached --quiet && exit 0 + git commit -m "Render ${repo_name} PDF for ${GITHUB_SHA::12} [skip actions]" + git push origin HEAD:deploy diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79c13e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +*.aux +*.log +*.out +*.fls +*.fdb_latexmk +*.synctex.gz +*.upa +*.upb +*.pdf +build-meta.tex +.rv/ +.stem/ +.nvim/ +.vim/ +.build/ +.gdb_history +host-build/ +build/*/prog.bin +build/*/prog.elf +build/*/prog.lst +build/*/prog.map +cmake-build-*/ +node_modules/ +__pycache__/ +*.pyc +vendor/Hazard3/ +vendor/lab-runtime/ +web/*.map +doc/session-reports/ +json/lesson_progress.json diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..06f142f --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +VERILATOR ?= verilator +TASK ?= task01_pin_polarity +BUILD_ROOT ?= .stem/build/rtl + +.PHONY: build test run trace clean + +build: + @tests/test_rtl.sh --build-only "$(TASK)" + +test: + @tests/test_rtl.sh "$(TASK)" + +run: test + +trace: + @tests/test_rtl.sh --trace "$(TASK)" + +clean: + @find "$(BUILD_ROOT)" -mindepth 1 -maxdepth 1 -type d -exec rm -r -- {} + diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c285a4 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# L02 · FPGA Bring-up and Programming + +Karta ustanawia bezpieczną drabinkę od kontraktu płytki do obserwacji LED: +polaryzacja sygnałów → działający blinker w symulacji → kabel → identyfikacja +układu → konfiguracja → fizyczna obserwacja. + +Repozytorium celowo zawiera [szablon kontraktu](board/board-contract.yaml), a +nie zmyślone piny. Przed budową bitstreamu operator musi podać rzeczywisty +model płytki, FPGA, częstotliwość, piny i programator. + +| Task | Automatyczny dowód | +| --- | --- | +| `task01_pin_polarity` | jawne active-low reset, button i LED | +| `task02_bringup_blinker` | znany reset i krawędzie LED co 4 takty | +| `task03_programming_gate` | kolejność cable → id → configure → observe | + +```bash +stemctl card use rv32i-asm L02 +stemctl test native-amd64 rv32i-asm L02 1 +stemctl test hazard3-sim rv32i-asm L02 2 +stemctl debug hazard3-sim rv32i-asm L02 3 +``` + +Komunikat `PASS task03` oznacza wyłącznie przejście modelu preflight i zawsze +raportuje `hardware_observed=0`. Nie jest dowodem zbudowania bitstreamu, +połączenia programatora ani działania fizycznej LED. Te dowody należy wpisać +do [answers/fpga-bringup-evidence.md](answers/fpga-bringup-evidence.md) po +wykonaniu na wskazanej płytce. diff --git a/answers/fpga-bringup-evidence.md b/answers/fpga-bringup-evidence.md new file mode 100644 index 0000000..d687510 --- /dev/null +++ b/answers/fpga-bringup-evidence.md @@ -0,0 +1,17 @@ +# Physical evidence · L02 FPGA Bring-up + +Automatyczne testy tej karty nie wypełniają poniższych pól. Uzupełnij je na +stanowisku operatorskim; bez nich nie deklaruj fizycznego uruchomienia. + +- Board ID / model: +- FPGA part: +- Toolchain and version: +- Constraint file commit: +- Bitstream path: +- SHA-256 bitstreamu: +- Programmer and detected device ID: +- Configure command and exit status: +- Expected LED period: +- Observed LED period: +- Observer and timestamp: +- Hardware result: `UNVERIFIED` / `PASS` / `FAIL` diff --git a/board/board-contract.yaml b/board/board-contract.yaml new file mode 100644 index 0000000..5d62cc2 --- /dev/null +++ b/board/board-contract.yaml @@ -0,0 +1,11 @@ +schema: 1 +status: template +board_id: SET_BOARD_ID +fpga_part: SET_FPGA_PART +toolchain: SET_BOARD_TOOLCHAIN +clock_hz: SET_CLOCK_HZ +clock_pin: SET_CLOCK_PIN +reset_pin: SET_RESET_PIN +led_pin: SET_LED_PIN +programmer: SET_PROGRAMMER +hardware_observed: false diff --git a/doc/assets/bringup-gate.png b/doc/assets/bringup-gate.png new file mode 100644 index 0000000000000000000000000000000000000000..de1814303b8cd1e8a5228db61cc5aaa3e8996af0 GIT binary patch literal 123773 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%0DA{OlxJ^*X!0@kUSsy;faJ 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 + Drabinka FPGA bring-upKolejne bramki to kontrakt płytki, symulacja, bitstream, identyfikacja urządzenia, konfiguracja i fizyczna obserwacja LED. + + + CONTRACTboard + pins + SIMRTL + VCD + BUILDbitstream + IDENTIFYdevice ID + CONFIGprogrammer + LEDobserve + AUTOMATED PASScontract + sim + preflight model + PHYSICAL EVIDENCE REQUIREDreal board, digest, device ID, log, observation + diff --git a/doc/generated/main.tex b/doc/generated/main.tex new file mode 100644 index 0000000..505ac5f --- /dev/null +++ b/doc/generated/main.tex @@ -0,0 +1,356 @@ +% Generated from json/card_source.json by tools/render_card.py. +% Do not edit manually; update the JSON and regenerate. + +\documentclass[12pt,a4paper]{article} + +\usepackage[T1]{fontenc} +\usepackage{lmodern} +\usepackage[utf8]{inputenc} +\usepackage[polish]{babel} +\usepackage{csquotes} +\usepackage{amsmath} +\usepackage{amssymb} +\usepackage{xcolor} +\usepackage[a4paper,left=2.15cm,right=2.15cm,top=0.5cm,bottom=1cm,headheight=28mm,headsep=3mm,includehead,includefoot]{geometry} +\usepackage{eso-pic} +\usepackage{marginnote} +\usepackage{array} +\usepackage{tabularx} +\usepackage{graphicx} +\usepackage{pdflscape} +\usepackage{float} +\usepackage{silence} +\WarningFilter{soulutf8}{This package is obsolete} +\usepackage{pdfcomment} +\usepackage{enumitem} +\usepackage{needspace} +\usepackage{fancyhdr} +\usepackage{lastpage} +\usepackage{microtype} +\usepackage[most]{tcolorbox} +\usepackage{qrcode} +\IfFileExists{references.bib}{% + \usepackage[backend=biber,style=numeric,sorting=none]{biblatex}% + \addbibresource{references.bib}% +}{} + +\hypersetup{hidelinks} + +\IfFileExists{build-meta.tex}{% + \input{build-meta.tex}% +}{% + \newcommand{\BuildCommit}{lokalna}% +} + +\newcommand{\DocumentAuthor}{M. Pabiszczak} +\newcommand{\DocumentYear}{2026} +\newcommand{\CardSeries}{rv32i-asm} +\newcommand{\CardSeriesTitle}{ASM · RV32I From Blinker to RISC-V} +\newcommand{\CardNumber}{02} +\newcommand{\CardCount}{09} +\newcommand{\CardSlug}{fpga-bringup} +\newcommand{\CardVersion}{v00.01} +\newcommand{\CardStatus}{Gotowa do wykonania} +\newcommand{\DocumentUUID}{31e9c2ad-f597-51ab-af6a-e550c3533f32} + +\newcommand{\EmptyCheck}{\(\square\)} +\newcommand{\EscAnswerLines}[1][3]{\par\noindent\dotfill\par\noindent\dotfill\par\noindent\dotfill} +\newcommand{\EscWriteRow}[1][2.8em]{\rule{0pt}{#1}} +\setlength{\parindent}{0pt} +\setlength{\parskip}{0.45em} +\setlength{\headheight}{28mm} +\setlength{\headsep}{3mm} +\setlength{\footskip}{8mm} +\setlength{\marginparwidth}{1.5cm} +\setlength{\marginparsep}{0.25cm} +\renewcommand{\arraystretch}{1.22} +% Margin separator lines disabled for layout trial. + +\newcommand{\ESCPageResourceHeader}{% + \begingroup% + \setlength{\parskip}{0pt}% + \setlength{\fboxsep}{0pt}% + \setlength{\fboxrule}{0.35pt}% + \setlength{\arrayrulewidth}{0.35pt}% + \noindent\fbox{% + \begin{minipage}[c][27.5mm][c]{\dimexpr\textwidth-2\fboxrule\relax}% + \begin{minipage}[c][27mm][c]{\dimexpr\linewidth-24mm-0.35pt\relax}% + \setlength{\tabcolsep}{0pt}% + \renewcommand{\arraystretch}{0}% + \begin{tabularx}{\linewidth}{@{}p{\dimexpr0.50000000\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.15126050\linewidth-\arrayrulewidth\relax}|X@{}}% + \parbox[c][6.11722mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries TITLE}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{7.7}{7.9}\selectfont\ttfamily\bfseries L02 · FPGA Bring-up and Programming}\hspace{0.45mm}}\par} & \parbox[c][6.11722mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries VER.}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{6.6}{6.8}\selectfont\ttfamily\bfseries 1}\hspace{0.45mm}}\par} & \parbox[c][6.11722mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries DATETIME}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{6.4}{6.6}\selectfont\ttfamily\bfseries 21.07.2026}\hspace{0.45mm}}\par} \\% + \end{tabularx}% + \par\nointerlineskip% + \hrule height0.35pt% + \nointerlineskip% + \begin{tabularx}{\linewidth}{@{}p{\dimexpr0.50000000\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.25210084\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.14285714\linewidth-\arrayrulewidth\relax}|X@{}}% + \parbox[c][6.11722mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries PROJECT}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{7.7}{7.9}\selectfont\ttfamily\bfseries From Blinker to RISC-V}\hspace{0.45mm}}\par} & \parbox[c][6.11722mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries SERIES}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{6.4}{6.6}\selectfont\ttfamily\bfseries ASM · RV32I From Blinker to RISC-V}\hspace{0.45mm}}\par} & \parbox[c][6.11722mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries CARD}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{6.4}{6.6}\selectfont\ttfamily\bfseries 02/09}\hspace{0.45mm}}\par} & \parbox[c][6.11722mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries SHEET}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{6.4}{6.6}\selectfont\ttfamily\bfseries \thepage/\pageref{LastPage}}\hspace{0.45mm}}\par} \\% + \end{tabularx}% + \par\nointerlineskip% + \hrule height0.35pt% + \nointerlineskip% + \begin{tabularx}{\linewidth}{@{}p{\dimexpr0.05882353\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.05042017\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.14705882\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.09243697\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.07563025\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.07563025\linewidth-\arrayrulewidth\relax}|X@{}}% + \parbox[c][6.11722mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries SUBJ.}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries Inf.}\hspace{0.15mm}}\par} & \parbox[c][6.11722mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries PROG.}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries —}\hspace{0.15mm}}\par} & \parbox[c][6.11722mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries CORE}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries —}\hspace{0.15mm}}\par} & \parbox[c][6.11722mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries SCOPE}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries —}\hspace{0.15mm}}\par} & \parbox[c][6.11722mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries LEVEL}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries —}\hspace{0.15mm}}\par} & \parbox[c][6.11722mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries POS.}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries —}\hspace{0.15mm}}\par} & \parbox[c][6.11722mm][c]{\linewidth}{\hbox to\linewidth{\hspace{0.35mm}{\fontsize{3.4}{3.75}\selectfont\ttfamily\bfseries GITEA UUID}\hfill{\fontsize{3.4}{3.75}\selectfont\ttfamily 31e9c2ad-f597-51ab-af6a-e550c3533f32}\hspace{0.35mm}}\par\nointerlineskip\hbox to\linewidth{\hspace{0.35mm}{\fontsize{3.4}{3.75}\selectfont\ttfamily\bfseries CARD UUID} {\fontsize{3.4}{3.75}\selectfont\ttfamily 31e9c2ad-f597-51ab-af6a-e550c3533f32}\hfill{\fontsize{3.4}{3.75}\selectfont\ttfamily\bfseries AUTHOR M. Pabiszczak}\hspace{0.35mm}}} \\% + \end{tabularx}% + \par\nointerlineskip% + \hrule height0.35pt% + \nointerlineskip% + \begin{tabularx}{\linewidth}{@{}X@{}}% + \parbox[c][4.07815mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{4.5}{4.85}\selectfont\ttfamily\bfseries GITEA} {\fontsize{4.5}{4.85}\selectfont\ttfamily\href{https://gitea.local/edu-inf/lab-rv32i-asm-fpga-bringup}{\nolinkurl{https://gitea.local/edu-inf/lab-rv32i-asm-fpga-bringup}}}} \\% + \end{tabularx}% + \par\nointerlineskip% + \hrule height0.35pt% + \nointerlineskip% + \begin{tabularx}{\linewidth}{@{}X@{}}% + \parbox[c][4.07815mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{4.5}{4.85}\selectfont\ttfamily\bfseries HTML} {\fontsize{4.5}{4.85}\selectfont\ttfamily\href{http://localhost:8080}{\nolinkurl{http://localhost:8080}}}} \\% + \end{tabularx}% + \end{minipage}% + \vrule width0.35pt% + \begin{minipage}[c][27mm][c]{24mm}\centering% + {\tiny QR wyłączony}% + \end{minipage}% + \end{minipage}% + }% + \endgroup% +} + +\pagestyle{fancy} +\fancyhf{} +\fancyhead[C]{\ESCPageResourceHeader} +\fancyfoot[L]{{\fontsize{6.4}{7.0}\selectfont\ttfamily UUID \DocumentUUID}} +\fancyfoot[C]{{\fontsize{6.4}{7.0}\selectfont\ttfamily \DocumentAuthor}} +\fancyfoot[R]{{\fontsize{6.4}{7.0}\selectfont\ttfamily STRONA \thepage/\pageref{LastPage}}} +\renewcommand{\headrulewidth}{0pt} +\renewcommand{\footrulewidth}{0.35pt} + +\newcommand{\ESCMarginTag}[4]{% + \hspace*{#1}\textcolor{#2}{#3~#4}% +} +\newcommand{\ESCSectionBlockStart}{% + \Needspace{8\baselineskip}% + \par\vspace{0.35em}% + \noindent\textcolor{black!28}{\rule{\textwidth}{0.35pt}}% + \par\vspace{0.15em}% +} +\newcommand{\ESCSectionBlockEnd}{% + \par\vspace{0.45em}% +} +\newcommand{\ESCTinyStepSeparator}{% + \par\vspace{0.10em}% + \noindent{\color{black!18}\leaders\hbox{\rule{0.60em}{0.22pt}\hspace{0.38em}}\hfill\kern0pt}% + \par\vspace{0.10em}% +} +\newtcolorbox{ESCTaskFrame}[1]{enhanced,breakable,arc=0pt,boxrule=0.35pt,colback=white,colframe=black,boxsep=0pt,left=1.4mm,right=1.4mm,top=0.8mm,bottom=0.8mm,colbacktitle=black!7,coltitle=black,title={\ttfamily\bfseries TASK\quad #1}} +\newtcolorbox{ESCBlockFrame}[1]{enhanced,breakable,arc=0pt,boxrule=0.35pt,colback=white,colframe=black!55,boxsep=0pt,left=1.0mm,right=1.0mm,top=0.6mm,bottom=0.6mm,colbacktitle=black!4,coltitle=black,title={\ttfamily\bfseries BLOCK\quad #1}} +\newtcolorbox{ESCStepFrame}[1]{enhanced,breakable,arc=0pt,boxrule=0.35pt,colback=white,colframe=black!28,boxsep=0pt,left=0.8mm,right=0.8mm,top=0.45mm,bottom=0.45mm,colbacktitle=black!2,coltitle=black,title={\ttfamily STEP\quad #1}} +\newtcolorbox{ESCConclusionFrame}{enhanced,breakable,arc=0pt,boxrule=0.45pt,colback=blue!2,colframe=blue!45!black,boxsep=0pt,left=1.2mm,right=1.2mm,top=0.7mm,bottom=0.7mm,colbacktitle=blue!7,coltitle=black,title={\ttfamily\bfseries WNIOSEK}} + +\newcommand{\ESCLearningTreeWidget}{% + \reversemarginpar + \marginnote[% + \begin{minipage}{\marginparwidth}% + \raggedright + {\fontsize{3.55}{3.95}\selectfont\ttfamily + \begin{minipage}[t]{\marginparwidth}% + \raggedright + {\bfseries\textcolor{black!65}{TECH}\par}% + \vspace{0.08em}% + \hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par% + \hspace*{0.28em}\textcolor{orange!85!black}{EK}\textcolor{black!48}{}\par% + \hspace*{0.54em}\textcolor{blue!70!black}{KW}\textcolor{black!48}{}\par% + \end{minipage}% + }% + \end{minipage}% + ]{} + \normalmarginpar + + \marginnote{% + \begin{minipage}{\marginparwidth}% + \raggedright + {\fontsize{3.55}{3.95}\selectfont\ttfamily + \hspace*{0.06cm}% + \begin{minipage}[t]{\dimexpr\marginparwidth-0.06cm\relax}% + \raggedright + {\bfseries\textcolor{black!65}{OG}\par}% + \vspace{0.08em}% + \hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par% + \hspace*{0.28em}\textcolor{green!50!black}{EN~~WO}\textcolor{black!48}{}\par% + \hspace*{0.54em}\textcolor{blue!70!black}{KW~~WS}\textcolor{black!48}{}\par% + \end{minipage}% + }% + \end{minipage}% + } + +} + +\begin{document} +\sloppy + +\vspace{1.0em} +\noindent{\Large\bfseries Cel karty}\par +\vspace{0.35em} +Uczeń przygotowuje jawny kontrakt płytki, weryfikuje top-level w symulacji i przechodzi uporządkowany preflight programowania. Automatyczny PASS nie jest dowodem działania fizycznego FPGA. + +\vspace{0.8em} +\noindent\textcolor{black!25}{\rule{\textwidth}{0.35pt}} +\vspace{0.7em} +\noindent{\Large\bfseries Zakres tasków}\par +\vspace{0.35em} + +\vspace{0.55em} +\small\begin{tabularx}{\textwidth}{@{}>{\ttfamily\raggedright\arraybackslash}p{1.10cm}>{\ttfamily\raggedright\arraybackslash}p{1.45cm}X>{\raggedright\arraybackslash}p{2.50cm}>{\raggedright\arraybackslash}p{1.75cm}>{\ttfamily\raggedright\arraybackslash}p{1.50cm}@{}} +\textbf{Krok} & \textbf{Numer tasku} & \textbf{Najważniejsza idea} & \textbf{Priorytet} & \textbf{Status} & \textbf{Version} \\ \hline +\texttt{2} & \texttt{Task01} & piny i active-low & obowiązkowe & opracowane & \texttt{v00.01} \\ \hline +\texttt{2} & \texttt{Task02} & reset i blinker RTL & obowiązkowe & opracowane & \texttt{v00.01} \\ \hline +\texttt{2} & \texttt{Task03} & ordered programming gate & obowiązkowe & opracowane & \texttt{v00.01} \\ \hline +\end{tabularx} +\normalsize + + +\vspace{0.8em} +\noindent\textcolor{black!25}{\rule{\textwidth}{0.35pt}} + +\clearpage + +\ESCSectionBlockStart +\section{Task01 · Kontrakt płytki i polaryzacja} +\reversemarginpar +\marginnote[% + \begin{minipage}{\marginparwidth}% + \raggedright + {\fontsize{3.55}{3.95}\selectfont\ttfamily + \begin{minipage}[t]{\marginparwidth}% + \raggedright + {\bfseries\textcolor{black!65}{TECH}\par}% + \vspace{0.08em}% + \hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par% + \end{minipage}% + }% + \end{minipage}% +]{}[-3.1em] +\normalmarginpar + +\marginnote{% + \begin{minipage}{\marginparwidth}% + \raggedright + {\fontsize{3.55}{3.95}\selectfont\ttfamily + \hspace*{0.06cm}% + \begin{minipage}[t]{\dimexpr\marginparwidth-0.06cm\relax}% + \raggedright + {\bfseries\textcolor{black!65}{OG}\par}% + \vspace{0.08em}% + \hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par% + \end{minipage}% + }% + \end{minipage}% +}[-3.1em] + + +\begingroup +\scriptsize\ttfamily\color{black!60}\sloppy +\noindent drzewka: \pdftooltip[width=\textwidth]{D1}{RV02.WE01.OG.IP.01 | WE 01: Board to observation. Bezpieczny i audytowalny FPGA bring-up. |\textCR EN IP RV02.01: Uczeń ocenia siłę dowodów bring-up. | KW IP RV02.01: Rozróżnia VCD od\textCR obserwacji fizycznej.} \pdftooltip[width=\textwidth]{D2}{RV02.WE01.TECH.I4.01 | WE 01: Board to observation. Bezpieczny i audytowalny FPGA bring-up.\textCR | EK INF04 RV02.01: Uczeń przygotowuje kontrakt i kolejność programowania. | KW INF04\textCR RV02.01: Nie omija identyfikacji urządzenia.}\par +\vspace{0.10em}% +\noindent K1: Potwierdź pin\_contract=generic i jawne polaryzacje.\quad D2\par +\par\vspace{0.18em}% +\endgroup + +Wypełnij board/board-contract.yaml dla konkretnego stanowiska. Potwierdź active-low reset, button i LED; wartości \texttt{SET\_*} są blokadą przeciw przypadkowemu użyciu obcych pinów. +\begin{figure}[H] + \centering + \includegraphics[width=0.72\linewidth]{\detokenize{../assets/bringup-gate.png}} + \caption{Symulacja kończy automatyczny preflight; fizyczny dowód wymaga rzeczywistej płytki.} + \label{fig:bringup-gate} +\end{figure} +\ESCSectionBlockEnd + +\ESCSectionBlockStart +\section{Task02 · Top-level blinker} +\reversemarginpar +\marginnote[% + \begin{minipage}{\marginparwidth}% + \raggedright + {\fontsize{3.55}{3.95}\selectfont\ttfamily + \begin{minipage}[t]{\marginparwidth}% + \raggedright + {\bfseries\textcolor{black!65}{TECH}\par}% + \vspace{0.08em}% + \hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par% + \end{minipage}% + }% + \end{minipage}% +]{}[-3.1em] +\normalmarginpar + +\marginnote{% + \begin{minipage}{\marginparwidth}% + \raggedright + {\fontsize{3.55}{3.95}\selectfont\ttfamily + \hspace*{0.06cm}% + \begin{minipage}[t]{\dimexpr\marginparwidth-0.06cm\relax}% + \raggedright + {\bfseries\textcolor{black!65}{OG}\par}% + \vspace{0.08em}% + \hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par% + \end{minipage}% + }% + \end{minipage}% +}[-3.1em] + + +\begingroup +\scriptsize\ttfamily\color{black!60}\sloppy +\noindent drzewka: \pdftooltip[width=\textwidth]{D1}{RV02.WE01.OG.IP.01 | WE 01: Board to observation. Bezpieczny i audytowalny FPGA bring-up. |\textCR EN IP RV02.01: Uczeń ocenia siłę dowodów bring-up. | KW IP RV02.01: Rozróżnia VCD od\textCR obserwacji fizycznej.} \pdftooltip[width=\textwidth]{D2}{RV02.WE01.TECH.I4.01 | WE 01: Board to observation. Bezpieczny i audytowalny FPGA bring-up.\textCR | EK INF04 RV02.01: Uczeń przygotowuje kontrakt i kolejność programowania. | KW INF04\textCR RV02.01: Nie omija identyfikacji urządzenia.}\par +\vspace{0.10em}% +\noindent K1: Zmierz reset\_known=1, cztery krawędzie i zapisz VCD.\quad D2\par +\par\vspace{0.18em}% +\endgroup + +Zasymuluj znany reset i dzielnik. LED musi zmienić stan w taktach 4, 8, 12 i 16. VCD dowodzi zachowania RTL, ale nie poprawności constraintów ani połączeń płytki. +\ESCSectionBlockEnd + +\ESCSectionBlockStart +\section{Task03 · Bramka programowania} +\reversemarginpar +\marginnote[% + \begin{minipage}{\marginparwidth}% + \raggedright + {\fontsize{3.55}{3.95}\selectfont\ttfamily + \begin{minipage}[t]{\marginparwidth}% + \raggedright + {\bfseries\textcolor{black!65}{TECH}\par}% + \vspace{0.08em}% + \hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par% + \end{minipage}% + }% + \end{minipage}% +]{}[-3.1em] +\normalmarginpar + +\marginnote{% + \begin{minipage}{\marginparwidth}% + \raggedright + {\fontsize{3.55}{3.95}\selectfont\ttfamily + \hspace*{0.06cm}% + \begin{minipage}[t]{\dimexpr\marginparwidth-0.06cm\relax}% + \raggedright + {\bfseries\textcolor{black!65}{OG}\par}% + \vspace{0.08em}% + \hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par% + \end{minipage}% + }% + \end{minipage}% +}[-3.1em] + + +\begingroup +\scriptsize\ttfamily\color{black!60}\sloppy +\noindent drzewka: \pdftooltip[width=\textwidth]{D1}{RV02.WE01.OG.IP.01 | WE 01: Board to observation. Bezpieczny i audytowalny FPGA bring-up. |\textCR EN IP RV02.01: Uczeń ocenia siłę dowodów bring-up. | KW IP RV02.01: Rozróżnia VCD od\textCR obserwacji fizycznej.} \pdftooltip[width=\textwidth]{D2}{RV02.WE01.TECH.I4.01 | WE 01: Board to observation. Bezpieczny i audytowalny FPGA bring-up.\textCR | EK INF04 RV02.01: Uczeń przygotowuje kontrakt i kolejność programowania. | KW INF04\textCR RV02.01: Nie omija identyfikacji urządzenia.}\par +\vspace{0.10em}% +\noindent K1: Potwierdź kolejność i brak fałszywego hardware claim.\quad D1\par +\par\vspace{0.18em}% +\endgroup + +Przejdź kolejno przez cable, device ID, configure i observe. Model blokuje pominięcie etapu, lecz jego PASS zawsze kończy się \texttt{hardware\_observed=0} oraz \texttt{claim=preflight-only}. Fizyczny wynik wpisz osobno. +\ESCSectionBlockEnd + +\end{document} diff --git a/doc/main.tex b/doc/main.tex new file mode 100644 index 0000000..6347d4d --- /dev/null +++ b/doc/main.tex @@ -0,0 +1,7 @@ +\newcommand{\PublisherDomain}{mpabi} +\newcommand{\CardArea}{inf} +\newcommand{\CardSeries}{rv32i-asm} +\newcommand{\CardNumber}{02} +\newcommand{\CardSlug}{fpga-bringup} +\newcommand{\CardVersion}{v00.01} +\newcommand{\DocumentUUID}{31e9c2ad-f597-51ab-af6a-e550c3533f32} diff --git a/doc/pdf/.gitkeep b/doc/pdf/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/json/card_source.json b/json/card_source.json new file mode 100644 index 0000000..268c112 --- /dev/null +++ b/json/card_source.json @@ -0,0 +1,22 @@ +{ + "$schema": "../../../../tools/card-layouts/schemas/card-source.schema.json", "schema": "esc-card-source.v1", + "card": {"id": "mpabi-inf-rv32i-asm-02-fpga-bringup", "series": "rv32i-asm", "series_title": "ASM · RV32I From Blinker to RISC-V", "number": "02", "count": "09", "slug": "fpga-bringup", "title": "L02 · FPGA Bring-up and Programming", "topic": "Kontrakt płytki, symulacja, programowanie i fizyczny dowód", "project": "From Blinker to RISC-V", "subject": "Informatyka", "level": "Rok 1 · RV32I", "revision_date": "2026-07-21T00:00:00+02:00", "status": "Gotowa do wykonania", "version": "v00.01", "uuid": "31e9c2ad-f597-51ab-af6a-e550c3533f32", "author": "M. Pabiszczak", "year": "2026"}, + "generated": {"tex": "doc/generated/main.tex", "html": "web/index.html", "html_css": "web/style.css", "react_app": true, "html_tree_inspector": false}, "template": "templates/karta-klasyczna.json", + "title_block": {"category": "KARTA PRACY · INFORMATYKA · FPGA", "standard": "ISO 7200", "prepared_by": "M. Pabiszczak", "prepared_on": "21.07.2026", "checked_by": "testy automatyczne + bramka sprzętowa", "approved_by": "—", "url": "http://localhost:8080", "repository_url": "https://gitea.local/edu-inf/lab-rv32i-asm-fpga-bringup", "revision": "1", "issued_on": "21.07.2026", "series": "RV32I-ASM-02", "document_type": "karta pracy", "tool": "card-layouts", "sheet": "1 / 1", "show_qr": false, "show_repository_qr": false, "height_cm": 3, "repeat_on_every_page": true, "replace_front_matter": true}, + "front_page_scope": {"title": "Cel karty", "content_tex": "Uczeń przygotowuje jawny kontrakt płytki, weryfikuje top-level w symulacji i przechodzi uporządkowany preflight programowania. Automatyczny PASS nie jest dowodem działania fizycznego FPGA.", "scope_title": "Zakres tasków", "scope_table": {"headers": ["Krok", "Numer tasku", "Najważniejsza idea", "Priorytet", "Status", "Version"], "rows": [{"chapter": "2", "task": "Task01", "idea_tex": "piny i active-low", "priority": "obowiązkowe", "status": "ready", "version": "v00.01"}, {"chapter": "2", "task": "Task02", "idea_tex": "reset i blinker RTL", "priority": "obowiązkowe", "status": "ready", "version": "v00.01"}, {"chapter": "2", "task": "Task03", "idea_tex": "ordered programming gate", "priority": "obowiązkowe", "status": "ready", "version": "v00.01"}]}}, + "learning_effects": {"RV02.EN01": {"text": "Uczeń analizuje granicę między symulacją, bitstreamem, programatorem i obserwacją sprzętu.", "label": "Bring-up evidence", "bloom_level": "Analiza", "assessment_criteria": ["RV02.KW01"]}, "RV02.EK01": {"text": "Uczeń przygotowuje top-level i uporządkowany workflow konfiguracji FPGA.", "label": "FPGA preflight", "bloom_level": "Zastosowanie", "assessment_criteria": ["RV02.KW01"]}}, + "assessment_criteria": {"RV02.KW01": {"text": "Polaryzacja, reset, krawędzie LED i kolejność cable–id–configure–observe są potwierdzone, a wynik modelu raportuje hardware_observed=0 i claim=preflight-only.", "learning_effects": ["RV02.EN01", "RV02.EK01"]}}, + "educational_requirements": {"RV02.WE01": {"text": "Bezpieczny i audytowalny FPGA bring-up.", "label": "Board to observation", "learning_effects": ["RV02.EN01", "RV02.EK01"], "learning_tree": {"schema": "we-learning-tree.v1", "policy": "Nie zgadujemy płytki ani pinów. Model i symulacja mogą zaliczyć preflight, lecz tylko log programatora i obserwacja na wskazanej płytce stanowią dowód fizyczny.", "ogolne": [{"tree_id": "RV02.WE01.OG.IP.01", "effect_ref": "RV02.EN01", "display": "EN IP RV02.01", "text": "Uczeń ocenia siłę dowodów bring-up.", "kw": [{"criterion_ref": "RV02.KW01", "display": "KW IP RV02.01", "text": "Rozróżnia VCD od obserwacji fizycznej."}]}], "zawodowe": [{"tree_id": "RV02.WE01.TECH.I4.01", "effect_ref": "RV02.EK01", "display": "EK INF04 RV02.01", "text": "Uczeń przygotowuje kontrakt i kolejność programowania.", "kw": [{"criterion_ref": "RV02.KW01", "display": "KW INF04 RV02.01", "text": "Nie omija identyfikacji urządzenia."}]}]}}}, + "sections": [ + {"title": "Task01 · Kontrakt płytki i polaryzacja", "content_kind": "prose", "content_tex": "Wypełnij board/board-contract.yaml dla konkretnego stanowiska. Potwierdź active-low reset, button i LED; wartości \\texttt{SET\\_*} są blokadą przeciw przypadkowemu użyciu obcych pinów.", "educational_requirement_refs": ["RV02.WE01"], "learning_effect_refs": ["RV02.EN01", "RV02.EK01"], "assessment_criterion_refs": ["RV02.KW01"], "area_tree_refs": ["RV02.WE01.OG.IP.01", "RV02.WE01.TECH.I4.01"], "steps": [{"id": "T01-PINS", "title": "Potwierdź pin_contract=generic i jawne polaryzacje.", "tree_refs": ["RV02.WE01.TECH.I4.01"]}], "assets": [{"path": "assets/bringup-gate.png", "html_path": "assets/bringup-gate.svg", "caption": "Symulacja kończy automatyczny preflight; fizyczny dowód wymaga rzeczywistej płytki.", "label": "fig:bringup-gate", "alt": "Diagram kolejnych bramek FPGA bring-up.", "kind": "diagram", "width": 0.72}]}, + {"title": "Task02 · Top-level blinker", "content_kind": "prose", "content_tex": "Zasymuluj znany reset i dzielnik. LED musi zmienić stan w taktach 4, 8, 12 i 16. VCD dowodzi zachowania RTL, ale nie poprawności constraintów ani połączeń płytki.", "educational_requirement_refs": ["RV02.WE01"], "learning_effect_refs": ["RV02.EN01", "RV02.EK01"], "assessment_criterion_refs": ["RV02.KW01"], "area_tree_refs": ["RV02.WE01.OG.IP.01", "RV02.WE01.TECH.I4.01"], "steps": [{"id": "T02-BLINK", "title": "Zmierz reset_known=1, cztery krawędzie i zapisz VCD.", "tree_refs": ["RV02.WE01.TECH.I4.01"]}]}, + {"title": "Task03 · Bramka programowania", "content_kind": "prose", "content_tex": "Przejdź kolejno przez cable, device ID, configure i observe. Model blokuje pominięcie etapu, lecz jego PASS zawsze kończy się \\texttt{hardware\\_observed=0} oraz \\texttt{claim=preflight-only}. Fizyczny wynik wpisz osobno.", "educational_requirement_refs": ["RV02.WE01"], "learning_effect_refs": ["RV02.EN01", "RV02.EK01"], "assessment_criterion_refs": ["RV02.KW01"], "area_tree_refs": ["RV02.WE01.OG.IP.01", "RV02.WE01.TECH.I4.01"], "steps": [{"id": "T03-GATE", "title": "Potwierdź kolejność i brak fałszywego hardware claim.", "tree_refs": ["RV02.WE01.OG.IP.01"]}]} + ], + "tasks": {"task01": {"prompt_tex": "Zweryfikuj polaryzację interfejsu płytki.", "criterion": "reset_active=1 button_active=1 led_n=0 pin_contract=generic", "assessment_criterion_ref": "RV02.KW01"}, "task02": {"prompt_tex": "Zweryfikuj reset i blinker.", "criterion": "reset_known=1 led_edges=4,8,12,16 divider=4", "assessment_criterion_ref": "RV02.KW01"}, "task03": {"prompt_tex": "Przejdź uporządkowany model programowania.", "criterion": "order=cable,id,configure,observe hardware_observed=0 claim=preflight-only", "assessment_criterion_ref": "RV02.KW01"}}, + "tasks_order": ["task01", "task02", "task03"], "mission": "Przygotować bezpieczny, nieudawany workflow od board contract do obserwacji LED.", "objectives": ["Zdefiniujesz płytkę i polaryzacje.", "Zweryfikujesz blinker przed syntezą.", "Oddzielisz preflight od fizycznego dowodu."], + "agenda": [{"time": "0--20 min", "work": "Kontrakt i piny."}, {"time": "20--40 min", "work": "Symulacja top-level."}, {"time": "40--70 min", "work": "Programowanie i formularz dowodowy."}], + "tech_stack": {"software": [{"name": "SystemVerilog + Verilator", "use": "preflight RTL i VCD"}, {"name": "Board-specific FPGA toolchain", "use": "realny bitstream po uzupełnieniu kontraktu"}, {"name": "stemctl", "use": "profile native-amd64 i hazard3-sim"}], "hardware": [{"name": "Płytka określona przez board-contract.yaml", "use": "fizyczna bramka poza automatycznym modelem"}]}, + "container_test": {"title": "Automatyczny preflight", "commands": ["stemctl test native-amd64 rv32i-asm L02 1", "stemctl debug hazard3-sim rv32i-asm L02 2", "stemctl test hazard3-sim rv32i-asm L02 3"], "pass_condition": "Trzy PASS, VCD oraz jawne hardware_observed=0; ten wynik nie jest dowodem fizycznego FPGA."}, + "safety_rules": [{"title": "Nie zgaduj pinów", "body": "Nie uruchamiaj syntezy ani programowania z wartościami SET_* w board-contract.yaml."}, {"title": "Identyfikuj przed konfiguracją", "body": "Odczytany device ID musi odpowiadać fpga_part kontraktu."}, {"title": "Nie zawyżaj dowodu", "body": "Symulacja, model workflow i plik VCD nie zastępują logu programatora oraz obserwacji LED."}], + "hardware_procedure": [{"step": "1", "action": "Uzupełnij board contract.", "condition": "Znane board ID, part, clock, piny i toolchain."}, {"step": "2", "action": "Zbuduj i zahashuj bitstream.", "condition": "Build zakończony, SHA-256 zapisany."}, {"step": "3", "action": "Wykryj i skonfiguruj FPGA.", "condition": "Device ID zgodny, programator exit 0."}, {"step": "4", "action": "Zmierz LED.", "condition": "Obserwator, czas i okres zapisane w evidence."}], "references": [] +} diff --git a/lesson-flow.yaml b/lesson-flow.yaml new file mode 100644 index 0000000..8349d00 --- /dev/null +++ b/lesson-flow.yaml @@ -0,0 +1,28 @@ +schema: 1 +card: rv32i-asm/L02 +title: FPGA Bring-up and Programming +steps: + - id: pins + task: task01_pin_polarity + action: test + profile: hazard3-sim + target: hazard3-baremetal + command: stemctl test hazard3-sim rv32i-asm L02 1 + board: {show: [board_contract, reset_n, button_n, led_n], highlight: [active_low, explicit_pin_map, no_guessed_board], question: Które sygnały są aktywne stanem niskim?} + evidence: [polarity, contract_status] + - id: blinker + task: task02_bringup_blinker + action: debug + profile: hazard3-sim + target: hazard3-baremetal + command: stemctl debug hazard3-sim rv32i-asm L02 2 + board: {show: [clock, reset_n, counter, led, trace_vcd], highlight: [known_reset, divider, edge_cycles], question: Dlaczego symulacja jest warunkiem koniecznym, ale nie dowodem działania płytki?} + evidence: [reset_known, led_edges, trace_vcd] + - id: physical-gate + task: task03_programming_gate + action: test + profile: hazard3-sim + target: hazard3-baremetal + command: stemctl test hazard3-sim rv32i-asm L02 3 + board: {show: [cable, device_id, bitstream_digest, configure_log, led_observation], highlight: [ordered_gate, physical_evidence, hardware_observed_zero_in_model], question: Jakiego dowodu nadal brakuje po PASS modelu?} + evidence: [workflow_order, preflight_claim, physical_form] diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4966070 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,811 @@ +{ + "name": "mpabi-inf-rv32i-asm-02-fpga-bringup", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mpabi-inf-rv32i-asm-02-fpga-bringup", + "dependencies": { + "@fastify/websocket": "^11.3.0", + "@msgpack/msgpack": "^3.1.3", + "fastify": "^5.6.0" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.5.tgz", + "integrity": "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" + } + }, + "node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.1.0.tgz", + "integrity": "sha512-PxcYtKLbQ8Z+yApiqjK8FwxIwvEj38k2OiLc17u8dkJSlmfi2wHHPaSnaoqBPQqtvF8YVsDgDpP2snDCfFrpfw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^7.0.0" + } + }, + "node_modules/@fastify/forwarded": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz", + "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@fastify/proxy-addr": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", + "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, + "node_modules/@fastify/websocket": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@fastify/websocket/-/websocket-11.3.0.tgz", + "integrity": "sha512-g89ag4BCcD9YP5wBZXixzoLnuf5j89p/sXFcfpCiv2pdEkYYukBEoK3heVzqsp0EAtszVDc2BBZG0KZqeAShIA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "duplexify": "^4.1.3", + "fastify-plugin": "^6.0.0", + "ws": "^8.16.0" + } + }, + "node_modules/@msgpack/msgpack": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.3.tgz", + "integrity": "sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA==", + "license": "ISC", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.3.0.tgz", + "integrity": "sha512-g2tQ7LE7oOSqDfwEm3M+ZCMTJc7KiZCdJ4UwyZJb5ckTKyYu50OYmvv0mCFXPuYXoM4zkSt8zM9XQ9KCvxA74A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-7.0.1.tgz", + "integrity": "sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^4.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-json-stringify/node_modules/fast-uri": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.1.0.tgz", + "integrity": "sha512-ZodJ2cRiLVWGi9IgPb3mbgSqM4CD3LexCHkuv0FfBXHJI1ADfucTD06m6clO2Cy5RZYsw/SiCVl/dyrFI/SYWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastify": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.10.0.tgz", + "integrity": "sha512-A9L0ziuWGQHgEEVgF3davQ9vbD93IuX+lo2IsxapQmu5b/Y/ynn9m9K5JHt9dvyJXOFc5iN0Zk5GHEOqnzhWjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.5", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^7.0.0", + "find-my-way": "^9.6.0", + "light-my-request": "^6.0.0", + "pino": "^9.14.0 || ^10.1.0", + "process-warning": "^5.0.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastify-plugin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-6.0.0.tgz", + "integrity": "sha512-fZOty7z3O7vOliF6d8bHE3wiEh1KcNnKEQensSgTk9C1DvN6nRLS++XVd86v33Hw/8u9Un8A1zDrQ8ujcQDHEg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/find-my-way": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.6.0.tgz", + "integrity": "sha512-Zf4Xve4RymLl7NgaavNebZ01joJ8MfVerOG43wy7SHLO+r+K0C6d/SE0BiR7AV5V1VOCFlOP7ecdo+I4qmiHrQ==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", + "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/json-schema-ref-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pino": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex2": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz", + "integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + }, + "bin": { + "safe-regex2": "bin/safe-regex2.js" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/thread-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz", + "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==", + "license": "MIT", + "dependencies": { + "real-require": "^1.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/thread-stream/node_modules/real-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz", + "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==", + "license": "MIT" + }, + "node_modules/toad-cache": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.4.tgz", + "integrity": "sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..698e4d5 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "mpabi-inf-rv32i-asm-02-fpga-bringup", + "private": true, + "type": "module", + "scripts": { + "dev": "node scripts/card_dev.mjs", + "serve": "node scripts/card_server.mjs", + "report": "node scripts/export_lesson_report.mjs", + "test": "node --test tests/*.test.mjs" + }, + "dependencies": { + "@fastify/websocket": "^11.3.0", + "@msgpack/msgpack": "^3.1.3", + "fastify": "^5.6.0" + } +} diff --git a/scripts/card_dev.mjs b/scripts/card_dev.mjs new file mode 100644 index 0000000..324642f --- /dev/null +++ b/scripts/card_dev.mjs @@ -0,0 +1,75 @@ +import { watch } from 'node:fs'; +import { access } from 'node:fs/promises'; +import path from 'node:path'; +import { spawn } from 'node:child_process'; + +const repositoryRoot = path.resolve(process.cwd()); +const layoutsRoot = path.resolve(process.env.CARD_LAYOUTS_ROOT ?? path.join(repositoryRoot, '../../../tools/card-layouts')); +const renderScript = path.join(repositoryRoot, 'scripts/render_card_layouts.sh'); +const watched = [ + [path.join(repositoryRoot, 'json'), name => name === 'card_source.json'], + [path.join(repositoryRoot, 'doc/assets'), () => true], + [path.join(layoutsRoot, 'react/src'), name => /\.(tsx?|css)$/.test(name)], + [path.join(layoutsRoot, 'react'), name => /^(build\.mjs|tsconfig\.json)$/.test(name)], + [path.join(layoutsRoot, 'tools'), name => name === 'render_card.py'], + [path.join(layoutsRoot, 'schemas'), name => name === 'card-source.schema.json'] +]; + +let build = null; +let queued = false; +let debounce = null; + +function render() { + if (build) { + queued = true; + return; + } + process.stdout.write('[card-dev] generowanie karty…\n'); + build = spawn(renderScript, { + cwd: repositoryRoot, + env: { ...process.env, CARD_LAYOUTS_ROOT: layoutsRoot }, + stdio: 'inherit' + }); + build.once('exit', code => { + build = null; + process.stdout.write(code === 0 + ? '[card-dev] karta gotowa; przeglądarka odświeży się automatycznie.\n' + : `[card-dev] generator zakończył się kodem ${code}.\n`); + if (queued) { + queued = false; + render(); + } + }); +} + +function scheduleRender() { + clearTimeout(debounce); + debounce = setTimeout(render, 180); +} + +await access(renderScript); +for (const [directory, accepts] of watched) { + try { + await access(directory); + watch(directory, (_event, filename) => { + if (filename && accepts(String(filename))) scheduleRender(); + }); + process.stdout.write(`[card-dev] obserwuję ${directory}\n`); + } catch { + process.stderr.write(`[card-dev] pomijam brakujący katalog ${directory}\n`); + } +} + +const server = spawn(process.execPath, ['--watch', 'scripts/card_server.mjs'], { + cwd: repositoryRoot, + stdio: 'inherit' +}); +const stop = signal => { + if (build) build.kill(signal); + server.kill(signal); + process.exit(0); +}; +process.on('SIGINT', () => stop('SIGINT')); +process.on('SIGTERM', () => stop('SIGTERM')); + +render(); diff --git a/scripts/card_server.mjs b/scripts/card_server.mjs new file mode 100644 index 0000000..808d0c5 --- /dev/null +++ b/scripts/card_server.mjs @@ -0,0 +1,918 @@ +import { createHash, randomUUID } from 'node:crypto'; +import { execFile } from 'node:child_process'; +import { createServer } from 'node:http'; +import { mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { promisify } from 'node:util'; +import Fastify from 'fastify'; +import websocket from '@fastify/websocket'; +import { + STATUSES, + catalogFromCard, + normaliseProgress, + progressSummary, + readJson, + statusLabel, + teamsMarkdown, + updateItem, + writeJsonAtomically +} from './lib/card_progress.mjs'; +import { + attachNvimUi, + callSelectedNvim, + resizeSelectedNvimUi, + selectedNvimTarget +} from './lib/nvim_ui_bridge.mjs'; +import { activateCheckpoint, checkpointStatus } from './lib/checkpoint_controller.mjs'; +import { CardStateDatabase } from './lib/card_state_db.mjs'; +import { + activateNavigation, + KEYBOARD_SHORTCUTS, + moveNavigation, + navigationCatalog, + normalizeViewerState, + patchViewerState, + selectNavigation +} from './lib/card_control.mjs'; + +const repositoryRoot = path.resolve(process.cwd()); +const cardFile = path.join(repositoryRoot, 'json/card_source.json'); +const progressFile = path.resolve(process.env.CARD_PROGRESS_FILE ?? path.join(repositoryRoot, 'json/lesson_progress.json')); +const stateDatabaseFile = path.resolve( + process.env.CARD_STATE_DATABASE ?? path.join(repositoryRoot, '.stem/card-state.sqlite3') +); +const apiSocket = path.resolve( + process.env.CARD_API_SOCKET ?? path.join(repositoryRoot, '.stem/card-api.sock') +); +const evidenceRoot = path.resolve( + process.env.CARD_EVIDENCE_ROOT ?? path.join(repositoryRoot, '.stem/evidence') +); +const webRoot = path.join(repositoryRoot, 'web'); +const host = process.env.CARD_HOST ?? '127.0.0.1'; +const port = Number(process.env.CARD_PORT ?? 8080); +const app = Fastify({ logger: true }); +const execFileAsync = promisify(execFile); +const stateDatabase = new CardStateDatabase(stateDatabaseFile); +await app.register(websocket, { + options: { + maxPayload: 64 * 1024 + } +}); +const contentTypes = { + '.css': 'text/css; charset=utf-8', + '.html': 'text/html; charset=utf-8', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.js': 'text/javascript; charset=utf-8', + '.json': 'application/json; charset=utf-8', + '.png': 'image/png', + '.puml': 'text/plain; charset=utf-8', + '.svg': 'image/svg+xml; charset=utf-8', + '.webp': 'image/webp' +}; + +let writeQueue = Promise.resolve(); +let currentNavigation = stateDatabase.read('navigation'); +let navigationRevision = Number.isSafeInteger(Number(currentNavigation?.revision)) + ? Number(currentNavigation.revision) + : 0; +const persistedViewer = stateDatabase.read('viewer'); +let currentViewer = normalizeViewerState(persistedViewer); +if (persistedViewer && JSON.stringify(persistedViewer) !== JSON.stringify(currentViewer)) { + stateDatabase.write('viewer', currentViewer); +} +let viewerRevision = Number.isSafeInteger(Number(currentViewer?.revision)) + ? Number(currentViewer.revision) + : 0; + +function actor(value, fallback = 'system') { + return typeof value === 'string' && value.trim() + ? value.trim().slice(0, 160) + : fallback; +} + +async function zoomSelectedHostPane() { + const selected = await selectedNvimTarget(); + const instance = String(selected.metadata?.instance ?? ''); + const result = await execFileAsync('tmux', [ + 'list-panes', '-a', '-F', + '#{pane_id}\t#{@stem_instance}\t#{@stem_role}\t#{window_zoomed_flag}' + ]); + const panes = result.stdout.trim().split('\n').filter(Boolean).map(line => { + const [id, stemInstance, role, zoomed] = line.split('\t'); + return { id, stemInstance, role, zoomed }; + }); + const exact = panes.find(pane => pane.stemInstance === instance && pane.role === 'debugger'); + const debuggerPanes = panes.filter(pane => pane.role === 'debugger'); + const pane = exact ?? (debuggerPanes.length === 1 ? debuggerPanes[0] : null); + if (!pane) return { found: false, instance }; + if (pane.zoomed !== '1') { + await execFileAsync('tmux', ['resize-pane', '-Z', '-t', pane.id]); + } + const dimensions = await execFileAsync('tmux', [ + 'display-message', '-p', '-t', pane.id, + '#{pane_width}x#{pane_height}\t#{window_zoomed_flag}' + ]); + const [size, zoomed] = dimensions.stdout.trim().split('\t'); + return { found: true, pane: pane.id, instance, size, zoomed: zoomed === '1' }; +} + +function expectedNvimGrid(hostPane) { + const match = hostPane?.found && typeof hostPane.size === 'string' + ? /^(\d+)x(\d+)$/.exec(hostPane.size) + : null; + if (!match) return null; + return { + width: Number(match[1]), + // The nested tmux status line occupies one row below the Neovim pane. + height: Math.max(1, Number(match[2]) - 1) + }; +} + +async function settleSelectedNvimTerminalUi(hostPane) { + const target = expectedNvimGrid(hostPane); + let innerTmux = null; + try { + innerTmux = await callSelectedNvim('nvim_exec_lua', [` + if not vim.env.TMUX or vim.env.TMUX == '' then + return { attempted = false } + end + local result = vim.system( + { 'tmux', 'resize-window', '-A' }, + { text = true } + ):wait() + return { + attempted = true, + code = result.code, + stderr = vim.trim(result.stderr or '') + } + `, []]); + } catch (error) { + innerTmux = { attempted: true, error: error instanceof Error ? error.message : String(error) }; + } + + const deadline = Date.now() + 1800; + let terminal = null; + do { + const uis = await callSelectedNvim('nvim_list_uis', []); + terminal = Array.isArray(uis) + ? uis.find(ui => ui?.stdin_tty && ui?.stdout_tty) ?? null + : null; + if ( + terminal + && (!target || (terminal.width >= target.width && terminal.height >= target.height)) + ) break; + await new Promise(resolve => setTimeout(resolve, 80)); + } while (Date.now() < deadline); + + return { + target, + terminal: terminal + ? { width: terminal.width, height: terminal.height } + : null, + inner_tmux: innerTmux, + settled: Boolean( + terminal + && (!target || (terminal.width >= target.width && terminal.height >= target.height)) + ) + }; +} + +function audit(eventType, eventActor, payload = {}, options = {}) { + return stateDatabase.appendEvent({ + eventType, + actor: actor(eventActor), + navigation: options.navigation === undefined ? currentNavigation : options.navigation, + evidenceRef: options.evidenceRef ?? null, + payload + }); +} + +function html(value) { + return String(value ?? '') + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"'); +} + +function auditReportHtml(events) { + const rows = events.map(event => { + const position = [event.task_id, event.block_id, event.phase_id, event.step_id] + .filter(Boolean) + .join(' / '); + const evidence = event.evidence_ref + ? `
${html(event.payload?.caption ?? event.event_type)}
${html(event.payload?.caption ?? '')}
` + : ''; + return `
${html(event.event_type)}${html(event.actor)}

${html(position)}

${evidence}
`; + }).join('\n'); + return `Ślad lekcji

Chronologiczny ślad lekcji

Wpisy i timestampy pochodzą z backendu; zrzuty są dowodami przypiętymi do zdarzeń.

${rows}
`; +} + +async function restoreNavigation() { + if (!currentNavigation) return; + try { + const catalog = await currentNavigationCatalog(); + currentNavigation = selectNavigation( + catalog, + currentNavigation, + navigationRevision, + currentNavigation.selected_at + ); + } catch { + currentNavigation = null; + navigationRevision = 0; + } +} + +async function currentNavigationCatalog() { + return navigationCatalog(await readJson(cardFile)); +} + +async function currentCardIdentity() { + const source = await readFile(cardFile); + const card = JSON.parse(source.toString('utf8')).card ?? {}; + return { + schema: 'stem-card-identity.v1', + id: String(card.id ?? ''), + uuid: String(card.uuid ?? ''), + version: String(card.version ?? ''), + source_sha256: createHash('sha256').update(source).digest('hex') + }; +} + +async function selectedNvimState() { + return callSelectedNvim('nvim_exec_lua', [` + local win = vim.api.nvim_get_current_win() + local buf = vim.api.nvim_win_get_buf(win) + local cursor = vim.api.nvim_win_get_cursor(win) + local mode = vim.api.nvim_get_mode() + return { + window = win, + buffer = buf, + path = vim.api.nvim_buf_get_name(buf), + cursor = { row = cursor[1], column = cursor[2] }, + mode = mode.mode, + blocking = mode.blocking, + changedtick = vim.api.nvim_buf_get_changedtick(buf), + columns = vim.o.columns, + lines = vim.o.lines + } + `, []]); +} + +async function webRevision() { + const files = ['index.html', 'app.js', 'app.css', 'style.css', 'card-data.json']; + for (const directory of ['figures', 'debug-snapshots']) { + try { + for (const name of await readdir(path.join(webRoot, directory))) { + files.push(path.join(directory, name)); + } + } catch { + // A card without optional generated assets is valid. + } + } + const revisions = await Promise.all(files.sort().map(async name => { + const info = await stat(path.join(webRoot, name)); + return `${name}:${info.mtimeMs}:${info.size}`; + })); + return revisions.join('|'); +} + +async function cardAndProgress() { + const card = await readJson(cardFile); + let saved; + try { + saved = await readJson(progressFile); + } catch (error) { + if (error.code !== 'ENOENT') throw error; + saved = {}; + } + return { + card, + progress: normaliseProgress(card, saved, { + repository: process.env.CARD_SOURCE_REPOSITORY, + revision: process.env.CARD_SOURCE_REVISION + }) + }; +} + +async function updateProgress(itemId, change) { + const operation = writeQueue.then(async () => { + const { card, progress } = await cardAndProgress(); + updateItem(progress, itemId, change); + await writeJsonAtomically(progressFile, progress); + return { card, progress }; + }); + writeQueue = operation.catch(() => undefined); + return operation; +} + +function trackerAssets() { + const css = ``; + const script = ``; + return { css, script }; +} + +function injectTracker(html, revision) { + const usesReactRenderer = html.includes(''); + const { css, script } = usesReactRenderer ? { css: '', script: '' } : trackerAssets(); + const reload = ``; + return html + .replace('', `${css}`) + .replace('', `${script}${reload}`); +} + +app.get('/api/progress', async () => { + const { card, progress } = await cardAndProgress(); + return { + card: progress.card, + session: progress.session, + catalog: catalogFromCard(card), + progress: { ...progress, summary: progressSummary(progress) }, + statuses: Object.fromEntries(STATUSES.map(status => [status, statusLabel[status]])) + }; +}); + +app.put('/api/progress/:itemId', async (request, reply) => { + const body = request.body && typeof request.body === 'object' ? request.body : {}; + if (!STATUSES.includes(body.status)) { + return reply.code(400).send({ error: `status musi być jednym z: ${STATUSES.join(', ')}` }); + } + try { + const progressActor = actor(body.actor, 'teacher'); + const { progress } = await updateProgress(request.params.itemId, { + status: body.status, + note: body.note, + actor: progressActor + }); + audit('progress.status', progressActor, { + item_id: request.params.itemId, + status: body.status, + note: typeof body.note === 'string' ? body.note.slice(0, 2000) : null + }); + return { progress: { ...progress, summary: progressSummary(progress) } }; + } catch (error) { + return reply.code(error.message.startsWith('Nieznany element') ? 404 : 500).send({ error: error.message }); + } +}); + +app.get('/api/report/teams.md', async (_request, reply) => { + const { card, progress } = await cardAndProgress(); + return reply + .type('text/markdown; charset=utf-8') + .header('content-disposition', `inline; filename="${progress.session.id}-teams.md"`) + .send(teamsMarkdown(card, progress)); +}); + +app.get('/api/report/lesson.html', async (_request, reply) => reply + .type('text/html; charset=utf-8') + .send(auditReportHtml(stateDatabase.listEvents({ limit: 5000 })))); + +app.get('/api/events', async request => { + const query = request.query && typeof request.query === 'object' ? request.query : {}; + const afterId = Math.max(0, Number.isSafeInteger(Number(query.after_id)) ? Number(query.after_id) : 0); + const limit = Math.max(1, Math.min(1000, Number.isSafeInteger(Number(query.limit)) ? Number(query.limit) : 200)); + return { + schema: 'stem-card-event-log.v1', + events: stateDatabase.listEvents({ afterId, limit }) + }; +}); + +app.post('/api/events', async (request, reply) => { + const body = request.body && typeof request.body === 'object' ? request.body : {}; + const eventType = typeof body.event_type === 'string' ? body.event_type.trim() : ''; + if (!/^[a-z][a-z0-9_.-]{1,79}$/.test(eventType)) { + return reply.code(400).send({ error: 'event_type ma niepoprawny format.' }); + } + const payload = body.payload && typeof body.payload === 'object' ? body.payload : {}; + if (JSON.stringify(payload).length > 16 * 1024) { + return reply.code(413).send({ error: 'payload zdarzenia jest zbyt duży.' }); + } + return { + schema: 'stem-card-event.v1', + event: audit(eventType, actor(body.actor, 'api'), payload) + }; +}); + +app.post('/api/evidence', { bodyLimit: 12 * 1024 * 1024 }, async (request, reply) => { + const body = request.body && typeof request.body === 'object' ? request.body : {}; + const match = typeof body.data_url === 'string' + ? /^data:image\/(png|jpeg|webp);base64,([A-Za-z0-9+/=]+)$/.exec(body.data_url) + : null; + if (!match) return reply.code(400).send({ error: 'Wymagany jest data_url obrazu PNG, JPEG albo WebP.' }); + const bytes = Buffer.from(match[2], 'base64'); + if (!bytes.length || bytes.length > 8 * 1024 * 1024) { + return reply.code(413).send({ error: 'Zrzut musi mieć od 1 B do 8 MiB.' }); + } + const extension = match[1] === 'jpeg' ? 'jpg' : match[1]; + const name = `${new Date().toISOString().replaceAll(':', '-')}-${randomUUID()}.${extension}`; + await mkdir(evidenceRoot, { recursive: true }); + await writeFile(path.join(evidenceRoot, name), bytes, { mode: 0o600 }); + const evidenceRef = `/api/evidence/${name}`; + const event = stateDatabase.appendEvent({ + eventType: 'checkpoint.screenshot', + actor: actor(body.actor, 'browser'), + navigation: currentNavigation, + evidenceRef, + payload: { + caption: typeof body.caption === 'string' ? body.caption.slice(0, 500) : '', + mime: `image/${match[1]}`, + size_bytes: bytes.length, + snapshot_ref: typeof body.snapshot_ref === 'string' ? body.snapshot_ref : currentNavigation?.snapshot_ref + } + }); + return reply.code(201).send({ schema: 'stem-card-evidence.v1', event }); +}); + +app.get('/api/evidence/:name', async (request, reply) => { + const name = String(request.params.name ?? ''); + if (!/^[A-Za-z0-9_.+-]+\.(png|jpg|webp)$/.test(name)) return reply.code(404).send('Not found'); + try { + const file = path.join(evidenceRoot, name); + const info = await stat(file); + if (!info.isFile()) return reply.code(404).send('Not found'); + return reply + .type(contentTypes[path.extname(file).toLowerCase()] ?? 'application/octet-stream') + .send(await readFile(file)); + } catch { + return reply.code(404).send('Not found'); + } +}); + +app.get('/api/card-revision', async () => ({ revision: await webRevision() })); + +app.get('/api/identity', async () => currentCardIdentity()); + +app.get('/api/checkpoint/status', async () => checkpointStatus()); + +app.get('/api/keyboard', async () => ({ + schema: 'stem-card-keyboard.v1', + hierarchy: ['card', 'task', 'block', 'phase', 'step', 'snapshot'], + shortcuts: KEYBOARD_SHORTCUTS +})); + +app.get('/api/navigation/catalog', async () => currentNavigationCatalog()); + +app.get('/api/navigation/current', async () => ({ + current: currentNavigation, + checkpoint: checkpointStatus() +})); + +app.put('/api/navigation/current', async (request, reply) => { + const body = request.body && typeof request.body === 'object' ? request.body : {}; + try { + const catalog = await currentNavigationCatalog(); + navigationRevision += 1; + currentNavigation = selectNavigation(catalog, body, navigationRevision); + stateDatabase.write('navigation', currentNavigation); + audit('navigation.select', currentNavigation.actor, { + focus_level: currentNavigation.focus_level, + sync_requested: currentNavigation.sync_requested + }); + return { current: currentNavigation }; + } catch (error) { + return reply.code(400).send({ + error: error instanceof Error ? error.message : String(error) + }); + } +}); + +app.post('/api/navigation/move', async (request, reply) => { + const body = request.body && typeof request.body === 'object' ? request.body : {}; + try { + const catalog = await currentNavigationCatalog(); + navigationRevision += 1; + currentNavigation = moveNavigation( + catalog, + currentNavigation, + body, + navigationRevision + ); + stateDatabase.write('navigation', currentNavigation); + audit('navigation.move', currentNavigation.actor, { + level: body.level ?? currentNavigation.focus_level, + delta: Number(body.delta), + sync_requested: false + }); + return { current: currentNavigation }; + } catch (error) { + return reply.code(400).send({ + error: error instanceof Error ? error.message : String(error) + }); + } +}); + +app.get('/api/viewer/state', async () => ({ viewer: currentViewer })); + +app.put('/api/viewer/state', async (request, reply) => { + const body = request.body && typeof request.body === 'object' ? request.body : {}; + try { + const requestedViewer = body.viewer && typeof body.viewer === 'object' ? body.viewer : body; + const requestedNvim = requestedViewer.nvim && typeof requestedViewer.nvim === 'object' + ? requestedViewer.nvim + : null; + viewerRevision += 1; + currentViewer = patchViewerState(currentViewer, body, viewerRevision); + stateDatabase.write('viewer', currentViewer); + if (requestedNvim && ['visible', 'sync', 'control', 'expanded', 'fit'].some(key => key in requestedNvim)) { + audit('viewer.nvim', actor(body.actor, 'browser'), { + visible: currentViewer.nvim.visible, + sync: currentViewer.nvim.sync, + control: currentViewer.nvim.control, + expanded: currentViewer.nvim.expanded, + fit: currentViewer.nvim.fit + }); + } + if (requestedViewer.allocator && typeof requestedViewer.allocator === 'object' + && 'visible' in requestedViewer.allocator) { + audit('viewer.allocator', actor(body.actor, 'browser'), { + visible: currentViewer.allocator.visible + }); + } + return { viewer: currentViewer }; + } catch (error) { + return reply.code(400).send({ + error: error instanceof Error ? error.message : String(error) + }); + } +}); + +app.get('/api/control/state', async () => ({ + schema: 'stem-card-control-state.v1', + revision: `${navigationRevision}:${viewerRevision}`, + navigation: currentNavigation, + viewer: currentViewer, + checkpoint: checkpointStatus() +})); + +app.get('/api/nvim/state', async (_request, reply) => { + try { + return { + schema: 'stem-card-nvim-state.v1', + state: await selectedNvimState() + }; + } catch (error) { + return reply.code(503).send({ + error: error instanceof Error ? error.message : String(error) + }); + } +}); + +app.put('/api/nvim/cursor', async (request, reply) => { + const body = request.body && typeof request.body === 'object' ? request.body : {}; + const row = Number(body.row); + const column = Number(body.column); + const requestedWindow = body.window == null ? null : Number(body.window); + if (!Number.isSafeInteger(row) || row < 1 || !Number.isSafeInteger(column) || column < 0) { + return reply.code(400).send({ error: 'Kursor wymaga row >= 1 i column >= 0.' }); + } + if (requestedWindow != null && (!Number.isSafeInteger(requestedWindow) || requestedWindow < 1)) { + return reply.code(400).send({ error: 'window musi być poprawnym identyfikatorem Neovima.' }); + } + try { + const window = requestedWindow ?? await callSelectedNvim('nvim_get_current_win'); + await callSelectedNvim('nvim_win_set_cursor', [window, [row, column]]); + return { + schema: 'stem-card-nvim-state.v1', + state: await selectedNvimState() + }; + } catch (error) { + return reply.code(503).send({ + error: error instanceof Error ? error.message : String(error) + }); + } +}); + +app.post('/api/nvim/input', async (request, reply) => { + const body = request.body && typeof request.body === 'object' ? request.body : {}; + const keys = typeof body.keys === 'string' ? body.keys : ''; + if (!keys || keys.length > 256) { + return reply.code(400).send({ error: 'keys musi zawierać od 1 do 256 znaków.' }); + } + try { + const accepted = await callSelectedNvim('nvim_input', [keys]); + return { accepted, state: await selectedNvimState() }; + } catch (error) { + return reply.code(503).send({ + error: error instanceof Error ? error.message : String(error) + }); + } +}); + +app.get('/api/state', async () => { + let nvim; + try { + nvim = { available: true, state: await selectedNvimState() }; + } catch (error) { + nvim = { available: false, error: error instanceof Error ? error.message : String(error) }; + } + return { + schema: 'stem-card-state.v1', + navigation: currentNavigation, + viewer: currentViewer, + checkpoint: checkpointStatus(), + nvim, + endpoints: { + keyboard: '/api/keyboard', + navigation_catalog: '/api/navigation/catalog', + navigation_current: '/api/navigation/current', + navigation_move: '/api/navigation/move', + navigation_sync: '/api/navigation/sync', + events: '/api/events', + evidence: '/api/evidence', + lesson_report: '/api/report/lesson.html', + identity: '/api/identity', + viewer: '/api/viewer/state', + nvim_reset: '/api/nvim/reset', + nvim_redraw: '/api/nvim/redraw', + nvim_cursor: '/api/nvim/cursor', + nvim_input: '/api/nvim/input' + } + }; +}); + +app.post('/api/checkpoint/activate', async (request, reply) => { + const body = request.body && typeof request.body === 'object' ? request.body : {}; + const eventActor = actor(body.actor, 'browser'); + audit('checkpoint.request', eventActor, { + snapshot_ref: body.snapshot_ref, + generation: Number(body.generation) + }); + try { + const result = await activateCheckpoint({ + repositoryRoot, + cardFile, + snapshotRef: body.snapshot_ref, + generation: Number(body.generation), + expectedBinding: body.expected_binding, + expectedIdentity: body.expected_identity + }); + audit('checkpoint.ready', eventActor, { + snapshot_ref: body.snapshot_ref, + generation: Number(body.generation), + status: result.status, + message: result.message + }); + return result; + } catch (error) { + audit('checkpoint.failed', eventActor, { + snapshot_ref: body.snapshot_ref, + generation: Number(body.generation), + message: error instanceof Error ? error.message : String(error) + }); + return reply.code(409).send({ + status: 'failed', + snapshot_ref: body.snapshot_ref, + message: error instanceof Error ? error.message : String(error) + }); + } +}); + +app.post('/api/nvim/reset', async (request, reply) => { + const body = request.body && typeof request.body === 'object' ? request.body : {}; + const eventActor = actor(body.actor, 'browser'); + audit('nvim.reset.request', eventActor, { source: 'web.F1' }); + try { + await callSelectedNvim('nvim_command', ['CpuReset']); + return { + schema: 'stem-card-nvim-reset.v1', + status: 'accepted', + actor: eventActor, + state: await selectedNvimState() + }; + } catch (error) { + audit('nvim.reset.failed', eventActor, { + source: 'web.F1', + message: error instanceof Error ? error.message : String(error) + }); + return reply.code(503).send({ + status: 'failed', + message: error instanceof Error ? error.message : String(error) + }); + } +}); + +app.post('/api/nvim/redraw', async (request, reply) => { + const body = request.body && typeof request.body === 'object' ? request.body : {}; + const eventActor = actor(body.actor, 'nvim'); + let hostPane; + try { + hostPane = await zoomSelectedHostPane(); + } catch (error) { + hostPane = { found: false, error: error instanceof Error ? error.message : String(error) }; + } + await new Promise(resolve => setTimeout(resolve, 120)); + try { + const terminalUi = await settleSelectedNvimTerminalUi(hostPane); + const externalUi = await resizeSelectedNvimUi(); + await callSelectedNvim('nvim_command', ['silent! StudentLayoutFit | redraw!']); + const state = await selectedNvimState(); + audit('nvim.redraw', eventActor, { + source: 'api', + host_pane: hostPane, + terminal_ui: terminalUi, + external_ui: externalUi, + columns: state.columns, + lines: state.lines + }); + return { + schema: 'stem-card-nvim-redraw.v1', + status: 'ready', + host_pane: hostPane, + terminal_ui: terminalUi, + external_ui: externalUi, + state + }; + } catch (error) { + return reply.code(503).send({ + status: 'failed', + host_pane: hostPane, + message: error instanceof Error ? error.message : String(error) + }); + } +}); + +app.post('/api/navigation/sync', async (request, reply) => { + const body = request.body && typeof request.body === 'object' ? request.body : {}; + const eventActor = actor(body.actor, 'nvim'); + const resolvedFrom = currentNavigation?.focus_level ?? null; + let activatedNavigation; + try { + const catalog = await currentNavigationCatalog(); + activatedNavigation = activateNavigation( + catalog, + currentNavigation, + eventActor, + navigationRevision + 1 + ); + } catch (error) { + return reply.code(409).send({ + status: 'failed', + message: error instanceof Error ? error.message : String(error) + }); + } + navigationRevision += 1; + currentNavigation = activatedNavigation; + stateDatabase.write('navigation', currentNavigation); + audit('navigation.activate', eventActor, { + source: 'navigation.sync', + resolved_from: resolvedFrom, + focus_level: currentNavigation.focus_level, + snapshot_ref: currentNavigation.snapshot_ref + }); + const snapshotRef = currentNavigation.snapshot_ref; + audit('checkpoint.request', eventActor, { + source: 'navigation.sync', + snapshot_ref: snapshotRef + }); + try { + const checkpoint = await activateCheckpoint({ + repositoryRoot, + cardFile, + snapshotRef, + generation: Date.now() + }); + audit('checkpoint.ready', eventActor, { + source: 'navigation.sync', + snapshot_ref: snapshotRef, + status: checkpoint.status, + message: checkpoint.message + }); + return { + schema: 'stem-card-navigation-sync.v1', + actor: eventActor, + current: currentNavigation, + checkpoint + }; + } catch (error) { + audit('checkpoint.failed', eventActor, { + source: 'navigation.sync', + snapshot_ref: snapshotRef, + message: error instanceof Error ? error.message : String(error) + }); + return reply.code(409).send({ + status: 'failed', + snapshot_ref: snapshotRef, + message: error instanceof Error ? error.message : String(error) + }); + } +}); + +app.get('/api/nvim-ui', { websocket: true }, (socket, request) => { + const origin = request.headers.origin; + const expectedOrigin = `http://${request.headers.host}`; + if (origin && origin !== expectedOrigin) { + socket.close(1008, 'Origin not allowed'); + return; + } + attachNvimUi(socket); +}); + +app.get('/*', async (request, reply) => { + const requested = request.params['*'] || 'index.html'; + const relative = requested === '' ? 'index.html' : requested; + const target = path.resolve(webRoot, relative); + if (target !== webRoot && !target.startsWith(`${webRoot}${path.sep}`)) return reply.code(403).send('Forbidden'); + try { + const info = await stat(target); + if (!info.isFile()) return reply.code(404).send('Not found'); + if (path.basename(target) === 'index.html') { + return reply + .type('text/html; charset=utf-8') + .send(injectTracker(await readFile(target, 'utf8'), await webRevision())); + } + return reply + .type(contentTypes[path.extname(target).toLowerCase()] ?? 'application/octet-stream') + .send(await readFile(target)); + } catch { + return reply.code(404).send('Not found'); + } +}); + +let socketServer = null; +app.addHook('onClose', async () => { + if (socketServer) { + await new Promise(resolve => socketServer.close(resolve)); + } + await rm(apiSocket, { force: true }); + stateDatabase.close(); +}); + +await restoreNavigation(); +await app.listen({ host, port }); + +await rm(apiSocket, { force: true }); +socketServer = createServer(app.routing); +await new Promise((resolve, reject) => { + socketServer.once('error', reject); + socketServer.listen(apiSocket, () => { + socketServer.off('error', reject); + resolve(); + }); +}); diff --git a/scripts/export_lesson_report.mjs b/scripts/export_lesson_report.mjs new file mode 100644 index 0000000..bba5cf6 --- /dev/null +++ b/scripts/export_lesson_report.mjs @@ -0,0 +1,107 @@ +import { mkdir, writeFile } from 'node:fs/promises'; +import { spawn } from 'node:child_process'; +import path from 'node:path'; +import { + catalogFromCard, + normaliseProgress, + readJson, + statusLabel, + teamsMarkdown +} from './lib/card_progress.mjs'; + +const root = path.resolve(process.cwd()); +const card = await readJson(path.join(root, 'json/card_source.json')); +const progressFile = path.resolve(process.env.CARD_PROGRESS_FILE ?? path.join(root, 'json/lesson_progress.json')); +let savedProgress; +try { + savedProgress = await readJson(progressFile); +} catch (error) { + if (error.code !== 'ENOENT') throw error; + savedProgress = {}; +} +const progress = normaliseProgress(card, savedProgress, { + repository: process.env.CARD_SOURCE_REPOSITORY, + revision: process.env.CARD_SOURCE_REVISION +}); +const reportDir = path.join(root, 'doc/session-reports'); +const basename = `${card.card.id}-${progress.session.id}`; +const markdownFile = path.join(reportDir, `${basename}.md`); +const texFile = path.join(reportDir, `${basename}.tex`); +const pdfFile = path.join(reportDir, `${basename}.pdf`); + +function tex(value) { + return String(value ?? '') + .replaceAll('\\', '\\textbackslash{}') + .replaceAll('&', '\\&').replaceAll('%', '\\%').replaceAll('$', '\\$') + .replaceAll('#', '\\#').replaceAll('_', '\\_').replaceAll('{', '\\{') + .replaceAll('}', '\\}').replaceAll('~', '\\textasciitilde{}') + .replaceAll('^', '\\textasciicircum{}'); +} + +function reportTex() { + const catalog = catalogFromCard(card); + const rows = catalog.map(item => { + const current = progress.items[item.id]; + return `${tex(item.id)} & ${tex(statusLabel[current.status])} & ${tex(current.updated_at || '—')} & ${tex(item.title)} \\\\ \\hline`; + }).join('\n'); + const events = progress.events.length + ? progress.events.map(event => `${tex(event.at)} & ${tex(event.item_id)} & ${tex(`${statusLabel[event.from]} → ${statusLabel[event.to]}`)} & ${tex(event.note || '—')} \\\\ \\hline`).join('\n') + : `— & — & Brak zmian & — \\\\ \\hline`; + return `\\documentclass[11pt,a4paper]{article} +\\usepackage[utf8]{inputenc} +\\usepackage[T1]{fontenc} +\\usepackage[polish]{babel} +\\usepackage[margin=18mm]{geometry} +\\usepackage{longtable} +\\usepackage{array} +\\usepackage{booktabs} +\\begin{document} +\\section*{${tex(card.card.title)} — zapis zajęć} +\\begin{tabular}{ll} +Karta & ${tex(card.card.id)} (${tex(card.card.version)}) \\\\ +Materiały & ${tex(progress.card.repository || 'nieuzupełniono')} \\\\ +Rewizja & ${tex(progress.card.revision || 'nieuzupełniono')} \\\\ +Sesja & ${tex(progress.session.id)} \\\\ +Prowadzący & ${tex(progress.session.teacher || 'nieuzupełniono')} \\\\ +Rozpoczęcie & ${tex(progress.session.started_at || 'brak')} \\\\ +\\end{tabular} + +\\subsection*{Zakres i status} +\\begin{longtable}{p{0.13\\textwidth}p{0.18\\textwidth}p{0.22\\textwidth}p{0.38\\textwidth}} +\\toprule Element & Status & Ostatnia zmiana & Zakres \\\\ \\midrule +${rows} +\\bottomrule +\\end{longtable} + +\\subsection*{Historia zatwierdzeń} +\\begin{longtable}{p{0.22\\textwidth}p{0.13\\textwidth}p{0.25\\textwidth}p{0.30\\textwidth}} +\\toprule Czas & Element & Zmiana & Notatka \\\\ \\midrule +${events} +\\bottomrule +\\end{longtable} +\\end{document} +`; +} + +function run(command, args, options) { + return new Promise((resolve, reject) => { + const child = spawn(command, args, options); + child.on('error', reject); + child.on('exit', code => code === 0 ? resolve() : reject(new Error(`${command} zakończył się kodem ${code}`))); + }); +} + +await mkdir(reportDir, { recursive: true }); +await writeFile(markdownFile, teamsMarkdown(card, progress), 'utf8'); +await writeFile(texFile, reportTex(), 'utf8'); +console.log(`Markdown: ${path.relative(root, markdownFile)}`); +console.log(`TeX: ${path.relative(root, texFile)}`); + +if (!process.argv.includes('--no-pdf')) { + try { + await run('latexmk', ['-pdf', '-interaction=nonstopmode', '-outdir=' + reportDir, texFile], { cwd: root, stdio: 'inherit' }); + console.log(`PDF: ${path.relative(root, pdfFile)}`); + } catch (error) { + console.warn(`Nie utworzono PDF (${error.message}). Pliki Markdown i TeX są gotowe.`); + } +} diff --git a/scripts/lib/card_control.mjs b/scripts/lib/card_control.mjs new file mode 100644 index 0000000..8f4a34f --- /dev/null +++ b/scripts/lib/card_control.mjs @@ -0,0 +1,376 @@ +const NAVIGATION_LEVELS = ['card', 'task', 'block', 'phase', 'step', 'snapshot']; + +export const KEYBOARD_SHORTCUTS = [ + { keys: 'Alt+ArrowUp/Alt+ArrowDown', action: 'navigation.move', level: 'task', description: 'poprzedni/następny TASK' }, + { keys: 'Ctrl+ArrowUp/Ctrl+ArrowDown', action: 'navigation.move', level: 'block', description: 'poprzedni/następny BLOCK' }, + { keys: 'Shift+ArrowUp/Shift+ArrowDown', action: 'navigation.move', level: 'phase', description: 'poprzednia/następna PHASE' }, + { keys: 'Ctrl+Shift+ArrowUp/Ctrl+Shift+ArrowDown', action: 'navigation.move', level: 'step', description: 'poprzedni/następny STEP' }, + { keys: 'Alt+Shift+ArrowUp/Alt+Shift+ArrowDown', action: 'navigation.move', level: 'snapshot', description: 'poprzedni/następny unikalny SNAPSHOT' }, + { keys: 'ArrowUp/ArrowDown', action: 'navigation.move', level: 'current', description: 'poprzedni/następny element aktywnego poziomu' }, + { keys: 'ArrowRight', action: 'navigation.level', direction: 'child', description: 'zejdź poziom niżej' }, + { keys: 'ArrowLeft', action: 'navigation.level', direction: 'parent', description: 'wróć poziom wyżej' }, + { keys: 'Enter', action: 'navigation.activate', description: 'wybierz element i przy SYNC ON odtwórz jego stan' }, + { keys: 'F1', action: 'nvim.reset', description: 'zresetuj aktualny cel i odśwież debugger' }, + { keys: 'F2', action: 'navigation.activate', description: 'zsynchronizuj cel z kursorem UML; BLOCK wybiera pierwszy krok' }, + { keys: 'Ctrl+Backquote', action: 'nvim.redraw', description: 'maksymalizuj pane, dopasuj siatkę i odśwież Neovima' }, + { keys: 'Ctrl+Enter', action: 'progress.toggle', description: 'zatwierdź lub cofnij zatwierdzenie bieżącego kroku' }, + { keys: 'Escape', action: 'navigation.level', direction: 'parent', description: 'anuluj albo wróć poziom wyżej' }, + { keys: 'Alt+Digit1', action: 'viewer.nvim.visible.toggle', description: 'pokaż/ukryj panel Neovima' }, + { keys: 'Alt+Digit2', action: 'viewer.nvim.sync.toggle', description: 'przełącz SYNC OFF/SYNC ON' }, + { keys: 'Alt+Digit3', action: 'viewer.nvim.control.toggle', description: 'uzbrój/rozbrój sterowanie Neovimem' }, + { keys: 'Alt+Digit4', action: 'viewer.nvim.expanded.toggle', description: 'przełącz normalną/wysoką wysokość panelu' }, + { keys: 'Alt+Digit5', action: 'viewer.nvim.fit.toggle', description: 'dopasuj cały ekran Neovima' }, + { keys: 'Alt+Digit6', action: 'viewer.allocator.visible.toggle', description: 'przypnij/ukryj model alokatora' }, + { keys: 'F12', action: 'viewer.keyboard.toggle', description: 'pokaż/ukryj skorowidz klawiatury' } +]; + +function text(value, fallback = '') { + return typeof value === 'string' && value.trim() ? value.trim() : fallback; +} + +function node(value, fallbackId, fallbackLabel) { + return { + id: text(value?.id, fallbackId), + label: text(value?.label, fallbackLabel) + }; +} + +export function navigationCatalog(card) { + const entries = []; + const taskIds = []; + const blockIds = new Map(); + const globalSnapshots = []; + + for (const [sectionIndex, section] of (card.sections ?? []).entries()) { + for (const [assetIndex, asset] of (section.assets ?? []).entries()) { + const interactive = asset.interactive; + if (!interactive?.phases?.length) continue; + const task = node( + interactive.task, + `section-${sectionIndex + 1}`, + text(section.title, `Sekcja ${sectionIndex + 1}`) + ); + const block = node( + interactive.block, + text(asset.label, `asset-${assetIndex + 1}`).replace(/^fig:/, ''), + text(interactive.title, text(asset.caption, `Blok ${assetIndex + 1}`)) + ); + if (!taskIds.includes(task.id)) taskIds.push(task.id); + const taskBlocks = blockIds.get(task.id) ?? []; + if (!taskBlocks.includes(block.id)) taskBlocks.push(block.id); + blockIds.set(task.id, taskBlocks); + const blockPhases = interactive.phases; + const blockSnapshots = []; + + for (const [phaseIndex, phaseValue] of blockPhases.entries()) { + const phase = node(phaseValue, `phase-${phaseIndex + 1}`, `Faza ${phaseIndex + 1}`); + const phaseSnapshots = []; + for (const [stepIndex, stepValue] of (phaseValue.steps ?? []).entries()) { + const step = node(stepValue, `step-${stepIndex + 1}`, `Krok ${stepIndex + 1}`); + const snapshotRef = text(stepValue.snapshot_ref) || null; + if (snapshotRef && !phaseSnapshots.includes(snapshotRef)) phaseSnapshots.push(snapshotRef); + if (snapshotRef && !blockSnapshots.includes(snapshotRef)) blockSnapshots.push(snapshotRef); + if (snapshotRef && !globalSnapshots.includes(snapshotRef)) globalSnapshots.push(snapshotRef); + entries.push({ + task: { ...task, index: taskIds.indexOf(task.id) }, + block: { ...block, index: taskBlocks.indexOf(block.id) }, + phase: { ...phase, index: phaseIndex }, + step: { + ...step, + number: Number.isSafeInteger(Number(stepValue.number)) ? Number(stepValue.number) : stepIndex + 1, + index: stepIndex, + global_index: entries.length + }, + snapshot: snapshotRef ? { + ref: snapshotRef, + index: phaseSnapshots.indexOf(snapshotRef), + block_index: blockSnapshots.indexOf(snapshotRef), + global_index: globalSnapshots.indexOf(snapshotRef) + } : null, + asset: { + label: text(asset.label), + section_index: sectionIndex, + asset_index: assetIndex + } + }); + } + } + } + } + + return { + schema: 'stem-card-navigation-catalog.v1', + levels: NAVIGATION_LEVELS, + entries + }; +} + +function integer(value) { + const parsed = Number(value); + return Number.isSafeInteger(parsed) ? parsed : null; +} + +function entryMatches(entry, request) { + const ids = { + task: text(request.task?.id, text(request.task_id)), + block: text(request.block?.id, text(request.block_id)), + phase: text(request.phase?.id, text(request.phase_id)), + step: text(request.step?.id, text(request.step_id)) + }; + for (const [level, id] of Object.entries(ids)) { + if (id && entry[level].id !== id) return false; + } + const snapshotRef = text(request.snapshot_ref, text(request.snapshot?.ref)); + return !snapshotRef || entry.snapshot?.ref === snapshotRef; +} + +function findNavigationEntry(catalog, request) { + const globalStep = integer(request.position?.global_step_index); + if (globalStep != null) return catalog.entries[globalStep] ?? null; + const matches = catalog.entries.filter(entry => entryMatches(entry, request)); + if (!matches.length) return null; + const phaseIndex = integer(request.position?.phase_index); + const stepIndex = integer(request.position?.step_index); + const snapshotIndex = integer(request.position?.snapshot_index); + return matches.find(entry => + (phaseIndex == null || entry.phase.index === phaseIndex) + && (stepIndex == null || entry.step.index === stepIndex) + && (snapshotIndex == null || entry.snapshot?.index === snapshotIndex) + ) ?? matches[0]; +} + +export function selectNavigation(catalog, request, revision = 1, now = new Date().toISOString()) { + if (!request || typeof request !== 'object') throw new Error('Stan nawigacji musi być obiektem.'); + const entry = findNavigationEntry(catalog, request); + if (!entry) throw new Error('Nie znaleziono wskazanej pozycji w katalogu karty.'); + const focusLevel = text(request.focus_level, 'step'); + if (!NAVIGATION_LEVELS.includes(focusLevel)) { + throw new Error(`focus_level musi być jednym z: ${NAVIGATION_LEVELS.join(', ')}.`); + } + return { + schema: 'stem-card-navigation.v2', + revision, + selected_at: now, + actor: text(request.actor, 'browser'), + focus_level: focusLevel, + cursor_visible: focusLevel !== 'card', + task: entry.task, + block: entry.block, + phase: entry.phase, + step: entry.step, + snapshot: entry.snapshot, + snapshot_ref: entry.snapshot?.ref ?? null, + position: { + task_index: entry.task.index, + block_index: entry.block.index, + phase_index: entry.phase.index, + step_index: entry.step.index, + global_step_index: entry.step.global_index, + snapshot_index: entry.snapshot?.index ?? null, + global_snapshot_index: entry.snapshot?.global_index ?? null + }, + sync_requested: request.sync_requested === true + }; +} + +function uniqueAtLevel(entries, level, current) { + const seen = new Set(); + return entries.filter(entry => { + let key; + if (level === 'task') key = entry.task.id; + else if (level === 'block') key = `${entry.task.id}/${entry.block.id}`; + else if (level === 'phase') key = `${entry.task.id}/${entry.block.id}/${entry.phase.id}`; + else if (level === 'snapshot') key = entry.snapshot?.ref; + else key = `${entry.task.id}/${entry.block.id}/${entry.phase.id}/${entry.step.id}`; + if (!key || seen.has(key)) return false; + seen.add(key); + if (level === 'phase') return entry.task.id === current.task.id && entry.block.id === current.block.id; + if (level === 'step' || level === 'snapshot') { + return entry.task.id === current.task.id && entry.block.id === current.block.id; + } + return true; + }); +} + +export function moveNavigation(catalog, current, command, revision = 1, now = new Date().toISOString()) { + if (!current) throw new Error('Najpierw ustaw bieżącą pozycję nawigacji.'); + const requestedLevel = text(command?.level, current.focus_level); + const level = requestedLevel === 'current' ? current.focus_level : requestedLevel; + if (!NAVIGATION_LEVELS.includes(level)) throw new Error('Niepoprawny poziom nawigacji.'); + if (level === 'card') throw new Error('Poziom CARD nie ma elementów UML do przewijania.'); + const delta = integer(command?.delta); + if (delta == null || delta === 0 || Math.abs(delta) > 100) throw new Error('delta musi być niezerową liczbą całkowitą.'); + const candidates = uniqueAtLevel(catalog.entries, level, current); + const currentIndex = candidates.findIndex(entry => { + if (level === 'task') return entry.task.id === current.task.id; + if (level === 'block') return entry.block.id === current.block.id && entry.task.id === current.task.id; + if (level === 'phase') return entry.phase.id === current.phase.id; + if (level === 'snapshot') return entry.snapshot?.ref === current.snapshot_ref; + return entry.step.id === current.step.id && entry.phase.id === current.phase.id; + }); + const targetIndex = Math.max(0, Math.min(candidates.length - 1, Math.max(0, currentIndex) + delta)); + const target = candidates[targetIndex]; + if (!target) throw new Error('Brak elementu na wskazanym poziomie.'); + return selectNavigation(catalog, { + task_id: target.task.id, + block_id: target.block.id, + phase_id: target.phase.id, + step_id: target.step.id, + snapshot_ref: target.snapshot?.ref, + focus_level: level, + // Moving the teaching cursor never mutates the debugger. Synchronisation + // is a separate, explicit activation (Enter in WWW or F2 in Neovim). + sync_requested: false, + actor: text(command.actor, 'api') + }, revision, now); +} + +export function activateNavigation(catalog, current, actor = 'nvim', revision = 1, now = new Date().toISOString()) { + if (!current) throw new Error('Najpierw wybierz BLOCK zawierający diagram UML.'); + if (current.focus_level === 'card' || current.focus_level === 'task') { + throw new Error('F2 wymaga zaznaczonego BLOCK, PHASE, STEP albo SNAPSHOT.'); + } + + const target = catalog.entries.find(entry => { + if (entry.task.id !== current.task.id || entry.block.id !== current.block.id) return false; + if (current.focus_level === 'block') return true; + if (entry.phase.id !== current.phase.id) return false; + if (current.focus_level === 'phase') return true; + if (current.focus_level === 'snapshot') return entry.snapshot?.ref === current.snapshot_ref; + return entry.step.id === current.step.id; + }); + if (!target) throw new Error('Nie znaleziono kroku UML, który można zsynchronizować.'); + if (!target.snapshot?.ref) throw new Error('Wybrany krok UML nie ma snapshotu do synchronizacji.'); + + return selectNavigation(catalog, { + task_id: target.task.id, + block_id: target.block.id, + phase_id: target.phase.id, + step_id: target.step.id, + snapshot_ref: target.snapshot.ref, + focus_level: 'step', + sync_requested: true, + actor: text(actor, 'nvim') + }, revision, now); +} + +export function initialViewerState() { + return { + schema: 'stem-card-viewer-state.v1', + revision: 0, + updated_at: null, + actor: null, + scale: 2.18, + scroll: { x: 0, y: 0, page_index: 0, sheet_scroll_top: 0 }, + viewport: { width: 0, height: 0 }, + allocator: { visible: true }, + nvim: { + visible: true, + sync: false, + control: false, + expanded: false, + fit: false, + connection: 'unknown', + screen_cursor: null, + grid: null + } + }; +} + +function finite(value, minimum, maximum, name) { + const parsed = Number(value); + if (!Number.isFinite(parsed) || parsed < minimum || parsed > maximum) { + throw new Error(`${name} jest poza zakresem ${minimum}..${maximum}.`); + } + return parsed; +} + +function boolean(value, fallback) { + return typeof value === 'boolean' ? value : fallback; +} + +export function patchViewerState(current, request, revision = current.revision + 1, now = new Date().toISOString()) { + if (!request || typeof request !== 'object') throw new Error('Stan viewer musi być obiektem.'); + const patch = request.viewer && typeof request.viewer === 'object' ? request.viewer : request; + const scroll = patch.scroll && typeof patch.scroll === 'object' ? patch.scroll : {}; + const viewport = patch.viewport && typeof patch.viewport === 'object' ? patch.viewport : {}; + const allocator = patch.allocator && typeof patch.allocator === 'object' ? patch.allocator : {}; + const nvim = patch.nvim && typeof patch.nvim === 'object' ? patch.nvim : {}; + const cursor = nvim.screen_cursor && typeof nvim.screen_cursor === 'object' + ? { + row: Math.trunc(finite(nvim.screen_cursor.row, 0, 100000, 'nvim.screen_cursor.row')), + column: Math.trunc(finite(nvim.screen_cursor.column, 0, 100000, 'nvim.screen_cursor.column')) + } + : current.nvim.screen_cursor; + const grid = nvim.grid && typeof nvim.grid === 'object' + ? { + columns: Math.trunc(finite(nvim.grid.columns, 1, 10000, 'nvim.grid.columns')), + rows: Math.trunc(finite(nvim.grid.rows, 1, 10000, 'nvim.grid.rows')) + } + : current.nvim.grid; + const sync = boolean(nvim.sync, current.nvim.sync); + const control = sync && boolean(nvim.control, current.nvim.control); + const visible = boolean(nvim.visible, current.nvim.visible); + const fit = visible && boolean(nvim.fit, current.nvim.fit); + const expanded = visible && !fit && boolean(nvim.expanded, current.nvim.expanded); + const pageIndex = scroll.page_index == null + ? current.scroll.page_index + : Math.trunc(finite(scroll.page_index, 0, 100000, 'scroll.page_index')); + const pageChanged = scroll.page_index != null && pageIndex !== current.scroll.page_index; + return { + schema: 'stem-card-viewer-state.v1', + revision, + updated_at: now, + actor: text(request.actor, 'browser'), + scale: patch.scale == null ? current.scale : finite(patch.scale, 0.25, 3, 'scale'), + scroll: { + x: scroll.x == null ? current.scroll.x : finite(scroll.x, 0, 10000000, 'scroll.x'), + y: scroll.y == null ? current.scroll.y : finite(scroll.y, 0, 10000000, 'scroll.y'), + page_index: pageIndex, + sheet_scroll_top: scroll.sheet_scroll_top == null + ? (pageChanged ? 0 : current.scroll.sheet_scroll_top ?? 0) + : finite(scroll.sheet_scroll_top, 0, 10000000, 'scroll.sheet_scroll_top') + }, + viewport: { + width: viewport.width == null ? current.viewport.width : finite(viewport.width, 0, 100000, 'viewport.width'), + height: viewport.height == null ? current.viewport.height : finite(viewport.height, 0, 100000, 'viewport.height') + }, + allocator: { + visible: boolean(allocator.visible, current.allocator?.visible ?? true) + }, + nvim: { + visible, + sync, + control, + expanded, + fit, + connection: text(nvim.connection, current.nvim.connection), + screen_cursor: cursor, + grid + } + }; +} + +export function normalizeViewerState(value) { + const initial = initialViewerState(); + if (!value || typeof value !== 'object') return initial; + const revisionValue = Number(value.revision); + const revision = Number.isSafeInteger(revisionValue) && revisionValue >= 0 + ? revisionValue + : 0; + const updatedAt = typeof value.updated_at === 'string' ? value.updated_at : null; + const actor = typeof value.actor === 'string' && value.actor.trim() + ? value.actor.trim() + : null; + try { + const normalized = patchViewerState( + initial, + { actor: actor ?? 'browser', viewer: value }, + revision, + updatedAt + ); + return { ...normalized, actor }; + } catch { + return { ...initial, revision, updated_at: updatedAt, actor }; + } +} diff --git a/scripts/lib/card_progress.mjs b/scripts/lib/card_progress.mjs new file mode 100644 index 0000000..74bd1b4 --- /dev/null +++ b/scripts/lib/card_progress.mjs @@ -0,0 +1,193 @@ +import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'; +import path from 'node:path'; + +export const STATUSES = ['pending', 'approved']; + +export const statusLabel = { + pending: 'niezatwierdzony', + approved: 'zatwierdzony' +}; + +function normaliseStatus(status) { + if (status === 'approved' || status === 'completed' || status === 'discussed') return 'approved'; + return 'pending'; +} + +export async function readJson(file) { + return JSON.parse(await readFile(file, 'utf8')); +} + +export async function writeJsonAtomically(file, value) { + await mkdir(path.dirname(file), { recursive: true }); + const temporary = `${file}.${process.pid}.${Date.now()}.tmp`; + await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, 'utf8'); + await rename(temporary, file); +} + +export function catalogFromCard(card) { + return card.sections.flatMap((section, sectionIndex) => { + const sectionSteps = (section.steps ?? []).map((step, stepIndex) => ({ + id: step.id, + title: step.title, + kind: 'section-step', + section_index: sectionIndex + 1, + section_title: section.title, + step_index: stepIndex + 1 + })); + const interactiveItems = (section.assets ?? []).flatMap(asset => { + const phases = asset.interactive?.phases ?? []; + if (phases.length) { + return phases.flatMap(phase => + (phase.steps ?? []) + .filter(step => typeof step.progress_id === 'string' && step.progress_id) + .map(step => ({ + id: step.progress_id, + title: `${String(asset.label || 'interaktywny UML').replace(/^fig:/, '')}: ${phase.label} / ${String(step.number).padStart(2, '0')} ${step.label}`, + kind: 'interactive-step', + section_index: sectionIndex + 1, + section_title: section.title, + asset_label: asset.label, + phase_id: phase.id, + step_id: step.id + })) + ); + } + return (asset.interactive?.stages ?? []) + .filter(stage => typeof stage.progress_id === 'string' && stage.progress_id) + .map(stage => ({ + id: stage.progress_id, + title: `${String(asset.label || 'interaktywny UML').replace(/^fig:/, '')}: ${stage.label}`, + kind: 'interactive-stage', + section_index: sectionIndex + 1, + section_title: section.title, + asset_label: asset.label, + stage_id: stage.id + })); + }).map((item, itemIndex) => ({ + ...item, + step_index: sectionSteps.length + itemIndex + 1 + })); + return [...sectionSteps, ...interactiveItems]; + }); +} + +export function normaliseProgress(card, progress, material = {}) { + const catalog = catalogFromCard(card); + const knownIds = new Set(catalog.map(item => item.id)); + const items = {}; + + for (const item of catalog) { + const previous = progress.items?.[item.id] ?? {}; + items[item.id] = { + status: normaliseStatus(previous.status), + updated_at: previous.updated_at ?? null, + note: typeof previous.note === 'string' ? previous.note : '' + }; + } + + return { + schema: 'stem-card-progress.v2', + card: { + id: card.card.id, + source: progress.card?.source || 'json/card_source.json', + version: card.card.version, + repository: progress.card?.repository || material.repository || '', + revision: progress.card?.revision || material.revision || '' + }, + session: { + id: progress.session?.id || `${new Date().toISOString().slice(0, 10)}-${card.card.slug}`, + title: progress.session?.title || card.card.title, + teacher: progress.session?.teacher || '', + started_at: progress.session?.started_at ?? null + }, + items, + events: Array.isArray(progress.events) + ? progress.events + .filter(event => knownIds.has(event.item_id)) + .map(event => ({ + ...event, + from: normaliseStatus(event.from), + to: normaliseStatus(event.to) + })) + : [] + }; +} + +export function progressSummary(progress) { + const counts = Object.fromEntries(STATUSES.map(status => [status, 0])); + for (const item of Object.values(progress.items)) counts[item.status] += 1; + return { total: Object.keys(progress.items).length, counts }; +} + +export function updateItem(progress, itemId, { status, note, actor = 'teacher', at = new Date().toISOString() }) { + if (!STATUSES.includes(status)) throw new Error(`Nieznany status: ${status}`); + const item = progress.items[itemId]; + if (!item) throw new Error(`Nieznany element karty: ${itemId}`); + + const nextNote = typeof note === 'string' ? note.trim() : item.note; + const changed = item.status !== status || item.note !== nextNote; + if (!changed) return false; + + const from = item.status; + item.status = status; + item.updated_at = at; + item.note = nextNote; + if (!progress.session.started_at) progress.session.started_at = at; + progress.events.push({ at, item_id: itemId, from, to: status, actor, note: nextNote }); + return true; +} + +function escapeMarkdown(value) { + return String(value ?? '').replaceAll('|', '\\|').replaceAll('\n', '
'); +} + +export function teamsMarkdown(card, progress) { + const catalog = catalogFromCard(card); + const byStatus = status => catalog.filter(item => progress.items[item.id]?.status === status); + const summary = progressSummary(progress); + const heading = `# ${card.card.title} — zapis zajęć`; + const metadata = [ + `- Karta: \`${card.card.id}\` (${card.card.version})`, + `- Materiał Edu: ${progress.card.repository || 'nieuzupełniono'}`, + `- Rewizja materiału: \`${progress.card.revision || 'nieuzupełniono'}\``, + `- Sesja: \`${progress.session.id}\``, + `- Prowadzący: ${progress.session.teacher || 'nieuzupełniono'}`, + `- Rozpoczęcie: ${progress.session.started_at || 'brak'}`, + `- Stan: ${summary.counts.approved}/${summary.total} zatwierdzone` + ]; + const list = (title, status) => { + const rows = byStatus(status); + const body = rows.length + ? rows.map(item => { + const state = progress.items[item.id]; + const timestamp = state.updated_at ? ` — ${state.updated_at}` : ''; + const note = state.note ? ` — ${state.note}` : ''; + return `- [${status === 'approved' ? 'x' : ' '}] \`${item.id}\` ${item.title}${timestamp}${note}`; + }).join('\n') + : '- Brak.'; + return `## ${title}\n\n${body}`; + }; + const timeline = progress.events.length + ? progress.events.map(event => { + const item = catalog.find(candidate => candidate.id === event.item_id); + return `| ${event.at} | \`${event.item_id}\` | ${statusLabel[event.from]} → ${statusLabel[event.to]} | ${escapeMarkdown(event.note)} |`; + }).join('\n') + : '| — | — | Brak zmian | — |'; + + return [ + heading, + '', + ...metadata, + '', + list('Zatwierdzone', 'approved'), + '', + list('Pozostało do zatwierdzenia', 'pending'), + '', + '## Historia zatwierdzeń', + '', + '| Czas | Element | Zmiana | Notatka |', + '| --- | --- | --- | --- |', + timeline, + '' + ].join('\n'); +} diff --git a/scripts/lib/card_state_db.mjs b/scripts/lib/card_state_db.mjs new file mode 100644 index 0000000..b0a89a1 --- /dev/null +++ b/scripts/lib/card_state_db.mjs @@ -0,0 +1,161 @@ +import { mkdirSync } from 'node:fs'; +import path from 'node:path'; +import { DatabaseSync } from 'node:sqlite'; + +const STATE_SCHEMA = 'stem-card-backend-state.v1'; + +export class CardStateDatabase { + constructor(file) { + this.file = path.resolve(file); + mkdirSync(path.dirname(this.file), { recursive: true }); + this.database = new DatabaseSync(this.file); + this.database.exec(` + PRAGMA journal_mode = WAL; + PRAGMA synchronous = NORMAL; + CREATE TABLE IF NOT EXISTS card_state ( + key TEXT PRIMARY KEY, + schema_name TEXT NOT NULL, + revision INTEGER NOT NULL, + updated_at TEXT NOT NULL, + value_json TEXT NOT NULL + ) STRICT; + CREATE TABLE IF NOT EXISTS card_event ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + occurred_at TEXT NOT NULL, + event_type TEXT NOT NULL, + actor TEXT NOT NULL, + task_id TEXT, + block_id TEXT, + phase_id TEXT, + step_id TEXT, + snapshot_ref TEXT, + evidence_ref TEXT, + payload_json TEXT NOT NULL + ) STRICT; + CREATE INDEX IF NOT EXISTS card_event_occurred_at + ON card_event(occurred_at, id); + `); + this.readStatement = this.database.prepare(` + SELECT value_json + FROM card_state + WHERE key = ? + `); + this.writeStatement = this.database.prepare(` + INSERT INTO card_state (key, schema_name, revision, updated_at, value_json) + VALUES (?, ?, ?, ?, ?) + ON CONFLICT(key) DO UPDATE SET + schema_name = excluded.schema_name, + revision = excluded.revision, + updated_at = excluded.updated_at, + value_json = excluded.value_json + `); + this.appendEventStatement = this.database.prepare(` + INSERT INTO card_event ( + occurred_at, event_type, actor, + task_id, block_id, phase_id, step_id, snapshot_ref, + evidence_ref, payload_json + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + RETURNING id + `); + this.listEventsStatement = this.database.prepare(` + SELECT + id, occurred_at, event_type, actor, + task_id, block_id, phase_id, step_id, snapshot_ref, + evidence_ref, payload_json + FROM card_event + WHERE id > ? + ORDER BY id ASC + LIMIT ? + `); + } + + read(key) { + const row = this.readStatement.get(key); + if (!row) return null; + try { + return JSON.parse(row.value_json); + } catch { + return null; + } + } + + write(key, value) { + const revision = Number.isSafeInteger(Number(value?.revision)) + ? Number(value.revision) + : 0; + const updatedAt = String(value?.updated_at ?? value?.selected_at ?? new Date().toISOString()); + this.writeStatement.run( + key, + STATE_SCHEMA, + revision, + updatedAt, + JSON.stringify(value) + ); + return value; + } + + appendEvent({ + eventType, + actor = 'system', + navigation = null, + evidenceRef = null, + payload = {}, + occurredAt = new Date().toISOString() + }) { + const row = this.appendEventStatement.get( + String(occurredAt), + String(eventType), + String(actor), + navigation?.task?.id ?? null, + navigation?.block?.id ?? null, + navigation?.phase?.id ?? null, + navigation?.step?.id ?? null, + navigation?.snapshot_ref ?? null, + evidenceRef == null ? null : String(evidenceRef), + JSON.stringify(payload ?? {}) + ); + return { + id: Number(row.id), + occurred_at: String(occurredAt), + event_type: String(eventType), + actor: String(actor), + task_id: navigation?.task?.id ?? null, + block_id: navigation?.block?.id ?? null, + phase_id: navigation?.phase?.id ?? null, + step_id: navigation?.step?.id ?? null, + snapshot_ref: navigation?.snapshot_ref ?? null, + evidence_ref: evidenceRef == null ? null : String(evidenceRef), + payload: payload ?? {} + }; + } + + listEvents({ afterId = 0, limit = 200 } = {}) { + return this.listEventsStatement.all(Number(afterId), Number(limit)).map(row => { + let payload = {}; + try { + payload = JSON.parse(row.payload_json); + } catch { + // Preserve the audit row even if an old payload cannot be decoded. + } + return { + id: Number(row.id), + occurred_at: row.occurred_at, + event_type: row.event_type, + actor: row.actor, + task_id: row.task_id, + block_id: row.block_id, + phase_id: row.phase_id, + step_id: row.step_id, + snapshot_ref: row.snapshot_ref, + evidence_ref: row.evidence_ref, + payload + }; + }); + } + + close() { + this.database.close(); + } +} + +export { STATE_SCHEMA }; diff --git a/scripts/lib/checkpoint_controller.mjs b/scripts/lib/checkpoint_controller.mjs new file mode 100644 index 0000000..84f2b39 --- /dev/null +++ b/scripts/lib/checkpoint_controller.mjs @@ -0,0 +1,423 @@ +import { createHash, randomUUID } from 'node:crypto'; +import { readFile, realpath } from 'node:fs/promises'; +import path from 'node:path'; +import { callSelectedNvim } from './nvim_ui_bridge.mjs'; + +const POLL_MS = 100; +const state = { + wantedGeneration: 0, + active: null, + activeControl: null, + last: null, + queue: Promise.resolve() +}; + +function delay(milliseconds) { + return new Promise(resolve => setTimeout(resolve, milliseconds)); +} + +function errorText(error) { + return error instanceof Error ? error.message : String(error); +} + +async function readJson(file) { + return JSON.parse(await readFile(file, 'utf8')); +} + +function runtimeRoot() { + return process.env.XDG_RUNTIME_DIR ?? '/run/user/1000'; +} + +function selectionRoot() { + return process.env.MCP_CONTAINER_SELECTION_ROOT + ?? path.join(runtimeRoot(), 'stem/mcp-selected'); +} + +async function selectedBinding() { + const selectedDirectory = await realpath(path.join(selectionRoot(), 'current')); + const [metadata, socketPath] = await Promise.all([ + readJson(path.join(selectedDirectory, 'selection.json')), + realpath(path.join(selectedDirectory, 'n.sock')) + ]); + return { + metadata, + socketPath, + epoch: `${metadata.container_id ?? 'unknown'}:${socketPath}` + }; +} + +async function assertBindingCurrent(expected) { + const current = await selectedBinding(); + if (current.epoch !== expected.epoch) { + throw new Error('Cel MCP zmienił się podczas replay; wynik starego kontenera został odrzucony.'); + } +} + +export function assertExpectedBinding(binding, expected) { + // Browser activation follows the currently selected teaching session and + // remains backward compatible. CLI/orchestrator callers pin all fields. + if (expected == null) return; + if (typeof expected !== 'object') throw new Error('expected_binding musi być obiektem.'); + for (const field of ['container_id', 'instance', 'profile', 'target']) { + const wanted = typeof expected[field] === 'string' ? expected[field] : ''; + const actual = typeof binding.metadata[field] === 'string' ? binding.metadata[field] : ''; + if (!wanted || wanted !== actual) { + throw new Error(`Cel MCP zmienił się przed replay (${field}: ${actual || '?'} != ${wanted || '?'}).`); + } + } +} + +export function assertExpectedIdentity(actual, expected) { + // Browser-only calls may omit the pin. Orchestrators send all fields so a + // card hot reload cannot silently replay a recipe from another revision. + if (expected == null) return; + if (typeof expected !== 'object') throw new Error('expected_identity musi być obiektem.'); + for (const field of ['id', 'uuid', 'version', 'source_sha256']) { + const wanted = typeof expected[field] === 'string' ? expected[field] : ''; + const observed = typeof actual[field] === 'string' ? actual[field] : ''; + if (!wanted || wanted !== observed) { + throw new Error(`Karta zmieniła się przed replay (${field}: ${observed || '?'} != ${wanted || '?'}).`); + } + } +} + +async function readCardSnapshot(cardFile) { + const source = await readFile(cardFile); + const document = JSON.parse(source.toString('utf8')); + const card = document.card ?? {}; + return { + document, + identity: { + id: String(card.id ?? ''), + uuid: String(card.uuid ?? ''), + version: String(card.version ?? ''), + source_sha256: createHash('sha256').update(source).digest('hex') + } + }; +} + +async function sha256(file) { + const hash = createHash('sha256'); + hash.update(await readFile(file)); + return hash.digest('hex'); +} + +async function gitBlobSha1(file) { + const content = await readFile(file); + const hash = createHash('sha1'); + hash.update(`blob ${content.length}\0`); + hash.update(content); + return hash.digest('hex'); +} + +function hostPath(repositoryRoot, containerPath) { + if (!path.isAbsolute(containerPath)) { + const resolved = path.resolve(repositoryRoot, containerPath); + if (resolved === repositoryRoot || resolved.startsWith(`${repositoryRoot}${path.sep}`)) { + return resolved; + } + } + if (containerPath === '/workspace') return repositoryRoot; + if (containerPath.startsWith('/workspace/')) { + return path.join(repositoryRoot, containerPath.slice('/workspace/'.length)); + } + throw new Error(`Ścieżka spoza /workspace nie jest dozwolona: ${containerPath}`); +} + +async function nvimLua(source, args = [], binding = null) { + return callSelectedNvim( + 'nvim_exec_lua', + [source, args], + binding ? { socketPath: binding.socketPath } : {} + ); +} + +async function callPinnedNvim(binding, method, parameters = []) { + return callSelectedNvim(method, parameters, { socketPath: binding.socketPath }); +} + +async function sendGdb(command, binding) { + const available = await callPinnedNvim(binding, 'nvim_call_function', [ + 'exists', + ['*TermDebugSendCommand'] + ]); + if (available !== 1) { + throw new Error('Termdebug nie udostępnia TermDebugSendCommand'); + } + return callPinnedNvim(binding, 'nvim_call_function', [ + 'TermDebugSendCommand', + [command] + ]); +} + +async function interruptGdb(binding) { + return nvimLua(` + for _, buffer in ipairs(vim.api.nvim_list_bufs()) do + local name = vim.api.nvim_buf_get_name(buffer) + if vim.bo[buffer].buftype == 'terminal' and name:find('gdb%-multiarch') then + local channel = vim.bo[buffer].channel + if channel and channel > 0 then + vim.api.nvim_chan_send(channel, string.char(3)) + return true + end + end + end + return false + `, [], binding); +} + +async function gdbTerminalRunning(binding) { + return nvimLua(` + for _, buffer in ipairs(vim.api.nvim_list_bufs()) do + local name = vim.api.nvim_buf_get_name(buffer) + if vim.bo[buffer].buftype == 'terminal' and name:find('gdb%-multiarch') then + local channel = vim.bo[buffer].channel + if channel and channel > 0 and vim.fn.jobwait({ channel }, 0)[1] == -1 then + return true + end + end + end + return false + `, [], binding); +} + +async function cleanupDeadGdbBuffers(binding) { + return nvimLua(` + local dead = {} + for _, buffer in ipairs(vim.api.nvim_list_bufs()) do + local name = vim.api.nvim_buf_get_name(buffer) + if vim.bo[buffer].buftype == 'terminal' and name:find('gdb%-multiarch') then + local channel = vim.bo[buffer].channel + if not channel or channel <= 0 or vim.fn.jobwait({ channel }, 0)[1] ~= -1 then + table.insert(dead, buffer) + end + end + end + for _, buffer in ipairs(dead) do + for _, window in ipairs(vim.fn.win_findbuf(buffer)) do + if vim.api.nvim_win_is_valid(window) and #vim.api.nvim_list_wins() > 1 then + pcall(vim.api.nvim_win_close, window, true) + end + end + if vim.api.nvim_buf_is_valid(buffer) then + pcall(vim.api.nvim_buf_delete, buffer, { force = true }) + end + end + return #dead + `, [], binding); +} + +async function ensureTermdebug(binding) { + const probe = gdbTerminalRunning(binding); + let running; + try { + running = await Promise.race([ + probe, + delay(500).then(() => { throw new Error('timeout'); }) + ]); + } catch { + // Termdebug cleanup sometimes leaves a hit-enter prompt which blocks an + // RPC expression while Neovim still accepts input events. + await callPinnedNvim(binding, 'nvim_input', ['']).catch(() => undefined); + running = await probe; + } + if (running) { + await cleanupDeadGdbBuffers(binding); + await callPinnedNvim(binding, 'nvim_command', ['silent! StudentLayoutFit']).catch(() => undefined); + return; + } + // A failed Termdebug job can leave Neovim at a hit-enter prompt. Feeding + // Enter only in the dead-GDB branch clears it before the recovery command. + await callPinnedNvim(binding, 'nvim_input', ['']).catch(() => undefined); + await cleanupDeadGdbBuffers(binding); + await callPinnedNvim(binding, 'nvim_command', ['silent! StudentTermdebug']); + const deadline = Date.now() + 8000; + while (Date.now() < deadline) { + if (await gdbTerminalRunning(binding)) { + // TermdebugStartPost queues target/directory/dashboard setup. + await delay(1200); + await cleanupDeadGdbBuffers(binding); + await callPinnedNvim(binding, 'nvim_command', ['silent! StudentLayoutFit']).catch(() => undefined); + return; + } + await delay(100); + } + throw new Error('Nie udało się automatycznie odtworzyć procesu GDB w Termdebug.'); +} + +async function assertEditorReady(repositoryRoot, artifact, binding) { + const modified = await nvimLua(` + local result = {} + for _, buffer in ipairs(vim.api.nvim_list_bufs()) do + if vim.api.nvim_buf_is_loaded(buffer) and vim.bo[buffer].modified then + table.insert(result, vim.api.nvim_buf_get_name(buffer)) + end + end + return result + `, [], binding); + if (Array.isArray(modified) && modified.some(name => name.endsWith(artifact.source))) { + throw new Error('Bufor Task04 ma niezapisane zmiany; zapis lub przebudowa są wymagane przed replay.'); + } + const elf = await nvimLua('return vim.env.ELF_FILE or ""', [], binding); + if (typeof elf !== 'string' || !elf) throw new Error('Neovim nie ma ELF_FILE bieżącej sesji.'); + const elfOnHost = hostPath(repositoryRoot, elf); + if (artifact.source_git_blob) { + const sourceOnHost = hostPath(repositoryRoot, artifact.source); + const actualSourceBlob = await gitBlobSha1(sourceOnHost); + if (actualSourceBlob !== artifact.source_git_blob) { + throw new Error('Źródło Task04 różni się od wersji użytej do pomiaru karty; przebuduj kartę i snapshoty.'); + } + } + if (artifact.hazard3_image_sha256 && binding.metadata.profile === 'hazard3-sim') { + const imageOnHost = hostPath(repositoryRoot, artifact.hazard3_image); + const actual = await sha256(imageOnHost); + if (actual !== artifact.hazard3_image_sha256) { + throw new Error('Obraz wykonywalny Hazard3 różni się od obrazu użytego do pomiaru karty; zregeneruj snapshoty.'); + } + } + return { elf, elfOnHost }; +} + +async function waitForResult(resultFile, operationId, timeoutMs, binding) { + const deadline = Date.now() + timeoutMs; + let latest = null; + while (Date.now() < deadline) { + try { + const current = await readJson(resultFile); + if (current.operation_id === operationId) { + latest = current; + state.active = current; + if (current.status === 'ready' || current.status === 'failed') return current; + } + } catch { + // GDB writes atomically; absence before the first phase is expected. + } + await delay(POLL_MS); + } + await interruptGdb(binding).catch(() => undefined); + await delay(200); + throw new Error(`Timeout replay; ostatni stan: ${latest?.status ?? 'brak odpowiedzi GDB'}`); +} + +async function runActivation({ + repositoryRoot, + cardFile, + snapshotRef, + generation, + expectedBinding, + expectedIdentity +}) { + if (generation !== state.wantedGeneration) { + return { status: 'cancelled', generation, snapshot_ref: snapshotRef, message: 'Zastąpione nowszym krokiem.' }; + } + const [cardSnapshot, binding] = await Promise.all([readCardSnapshot(cardFile), selectedBinding()]); + const card = cardSnapshot.document; + assertExpectedIdentity(cardSnapshot.identity, expectedIdentity); + assertExpectedBinding(binding, expectedBinding); + const selection = binding.metadata; + const registry = card.debug_checkpoints; + const recipe = registry?.items?.[snapshotRef]; + if (!recipe) throw new Error(`Brak recepty checkpointu: ${snapshotRef}`); + if (!registry.targets?.[selection.profile]) { + throw new Error(`Karta nie ma adaptera replay dla profilu ${selection.profile ?? '?'}.`); + } + await assertBindingCurrent(binding); + await ensureTermdebug(binding); + await assertEditorReady(repositoryRoot, registry.artifact, binding); + const operationId = randomUUID(); + const payload = { + operation_id: operationId, + generation, + snapshot_ref: snapshotRef, + profile: selection.profile, + semantics: registry.semantics, + stop: recipe.stop, + verify: recipe.verify ?? { expressions: [] } + }; + state.active = { + status: 'replaying', + phase: 'dispatch', + operation_id: operationId, + generation, + snapshot_ref: snapshotRef, + profile: selection.profile + }; + state.activeControl = { generation, binding }; + await sendGdb('source /workspace/tools/gdb/stem_checkpoint.py', binding); + const token = Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url'); + await sendGdb(`stem-checkpoint-activate ${token}`, binding); + const resultFile = path.join( + repositoryRoot, + '.stem/instances', + selection.instance, + 'gdb-sync.json.checkpoint.json' + ); + const result = await waitForResult( + resultFile, + operationId, + selection.profile === 'rp2350' ? 45000 : 20000, + binding + ); + if (result.status !== 'ready') { + throw new Error(result.message ?? `Replay ${snapshotRef} nie osiągnął stanu ready.`); + } + await assertBindingCurrent(binding); + state.last = result; + state.active = null; + state.activeControl = null; + await callPinnedNvim(binding, 'nvim_command', ['silent! StudentViewReset']).catch(() => undefined); + await callPinnedNvim(binding, 'nvim_command', ['redraw!']).catch(() => undefined); + return result; +} + +export function checkpointStatus() { + return { active: state.active, last: state.last, wanted_generation: state.wantedGeneration }; +} + +export function activateCheckpoint({ + repositoryRoot, + cardFile, + snapshotRef, + generation, + expectedBinding, + expectedIdentity +}) { + if (typeof snapshotRef !== 'string' || !/^task04\.[a-z0-9.-]+$/.test(snapshotRef)) { + return Promise.reject(new Error('Niepoprawny snapshot_ref.')); + } + if (generation != null && (!Number.isSafeInteger(generation) || generation <= 0)) { + return Promise.reject(new Error('generation musi być dodatnią, bezpieczną liczbą całkowitą.')); + } + const nextGeneration = Number.isSafeInteger(generation) && generation > 0 + ? generation + : state.wantedGeneration + 1; + state.wantedGeneration = Math.max(state.wantedGeneration + 1, nextGeneration); + const activeControl = state.activeControl; + if ( + activeControl + && activeControl.generation < state.wantedGeneration + && state.active?.phase === 'run-to-stop' + ) { + void interruptGdb(activeControl.binding).catch(() => undefined); + } + const request = { + repositoryRoot, + cardFile, + snapshotRef, + generation: state.wantedGeneration, + expectedBinding, + expectedIdentity + }; + const operation = state.queue.then(() => runActivation(request)); + state.queue = operation.catch(error => { + state.last = { + status: 'failed', + snapshot_ref: snapshotRef, + generation: request.generation, + message: errorText(error) + }; + state.active = null; + if (state.activeControl?.generation === request.generation) state.activeControl = null; + }); + return operation; +} diff --git a/scripts/lib/nvim_ui_bridge.mjs b/scripts/lib/nvim_ui_bridge.mjs new file mode 100644 index 0000000..2cecea2 --- /dev/null +++ b/scripts/lib/nvim_ui_bridge.mjs @@ -0,0 +1,447 @@ +import net from 'node:net'; +import path from 'node:path'; +import { readFile, realpath } from 'node:fs/promises'; +import { decodeMultiStream, encode } from '@msgpack/msgpack'; + +const OPEN = 1; +const DEFAULT_COLUMNS = 190; +const DEFAULT_ROWS = 50; +const RETRY_DELAY_MS = 1200; +const TARGET_POLL_MS = 750; +const MAX_BUFFERED_BYTES = 4 * 1024 * 1024; + +function errorText(error) { + return error instanceof Error ? error.message : String(error); +} + +function delay(milliseconds) { + return new Promise(resolve => setTimeout(resolve, milliseconds)); +} + +async function selectedTarget(selectionRoot) { + const current = path.join(selectionRoot, 'current'); + const socketLink = path.join(current, 'n.sock'); + const [socketPath, metadataText] = await Promise.all([ + realpath(socketLink), + readFile(path.join(current, 'selection.json'), 'utf8') + ]); + const metadata = JSON.parse(metadataText); + return { + socketPath, + metadata, + epoch: `${metadata.container_id ?? 'unknown'}:${socketPath}` + }; +} + +class NvimRpcClient { + constructor(socketPath, onNotification) { + this.socketPath = socketPath; + this.onNotification = onNotification; + this.socket = new net.Socket(); + this.nextMessageId = 1; + this.pending = new Map(); + this.closed = false; + this.readLoop = null; + } + + async connect() { + await new Promise((resolve, reject) => { + const connected = () => { + this.socket.off('error', failed); + resolve(); + }; + const failed = error => { + this.socket.off('connect', connected); + reject(error); + }; + this.socket.once('connect', connected); + this.socket.once('error', failed); + this.socket.connect(this.socketPath); + }); + this.readLoop = this.consume().catch(error => { + this.rejectPending(error); + if (!this.closed) this.socket.destroy(error); + throw error; + }); + } + + async consume() { + for await (const message of decodeMultiStream(this.socket)) { + if (!Array.isArray(message)) continue; + if (message[0] === 1) { + const pending = this.pending.get(message[1]); + if (!pending) continue; + this.pending.delete(message[1]); + if (message[2]) pending.reject(new Error(JSON.stringify(message[2]))); + else pending.resolve(message[3]); + } else if (message[0] === 2) { + this.onNotification(message[1], message[2]); + } + } + throw new Error('Neovim zamknął połączenie RPC.'); + } + + call(method, parameters = []) { + if (this.closed) return Promise.reject(new Error('Połączenie RPC jest zamknięte.')); + const id = this.nextMessageId++; + return new Promise((resolve, reject) => { + this.pending.set(id, { resolve, reject }); + this.socket.write(encode([0, id, method, parameters]), error => { + if (!error) return; + this.pending.delete(id); + reject(error); + }); + }); + } + + rejectPending(error) { + for (const pending of this.pending.values()) pending.reject(error); + this.pending.clear(); + } + + close() { + if (this.closed) return; + this.closed = true; + this.rejectPending(new Error('Połączenie RPC zostało zamknięte.')); + this.socket.destroy(); + } +} + +function safeSend(websocket, message) { + if (websocket.readyState !== OPEN) return; + if (websocket.bufferedAmount > MAX_BUFFERED_BYTES) { + websocket.close(1013, 'Neovim UI client is too slow'); + return; + } + websocket.send(JSON.stringify(message)); +} + +function existingUiSize(uis) { + const valid = Array.isArray(uis) + ? uis.filter(item => Number.isInteger(item?.width) && Number.isInteger(item?.height)) + : []; + const terminal = valid.find(item => item?.stdin_tty && item?.stdout_tty); + if (terminal) { + return { width: terminal.width, height: terminal.height }; + } + return { + // Without a terminal UI there is nothing to mirror, so retain a useful + // external-UI fallback size until the selected terminal is attached again. + width: Math.max(DEFAULT_COLUMNS, ...valid.map(item => item.width)), + height: Math.max(DEFAULT_ROWS, ...valid.map(item => item.height)) + }; +} + +class NvimUiHub { + constructor(selectionRoot, onEmpty) { + this.selectionRoot = selectionRoot; + this.onEmpty = onEmpty; + this.clients = new Set(); + this.rpc = null; + this.stopped = false; + this.started = false; + this.status = { type: 'status', state: 'connecting' }; + this.pendingResize = null; + this.resizeTimer = null; + this.appliedUiSize = ''; + this.uiSize = { width: DEFAULT_COLUMNS, height: DEFAULT_ROWS }; + this.uiAttached = false; + this.refreshingUi = null; + } + + broadcast(message) { + this.status = message.type === 'status' ? message : this.status; + for (const client of this.clients) safeSend(client, message); + } + + add(websocket) { + this.clients.add(websocket); + safeSend(websocket, this.status); + const remove = () => this.remove(websocket); + websocket.once('close', remove); + websocket.once('error', remove); + websocket.on('message', payload => this.handleMessage(payload)); + if (!this.started) { + this.started = true; + void this.run().catch(error => { + this.broadcast({ type: 'status', state: 'offline', message: errorText(error) }); + }); + } else if (this.rpc && this.uiAttached) { + // A newly opened browser has no copy of redraw events emitted before it + // joined the shared hub. Reattaching only this external UI makes Neovim + // send one complete line-grid frame. The real TTY UI stays attached. + void this.refreshUi().catch(() => undefined); + } + } + + refreshUi() { + if (this.refreshingUi) return this.refreshingUi; + const rpc = this.rpc; + const size = this.uiSize; + if (!rpc || !this.uiAttached) return Promise.resolve(); + this.refreshingUi = (async () => { + await rpc.call('nvim_ui_detach'); + if (rpc !== this.rpc || this.stopped) return; + this.uiAttached = false; + await rpc.call('nvim_ui_attach', [size.width, size.height, { + rgb: false, + ext_linegrid: true, + ext_multigrid: false, + override: false + }]); + if (rpc === this.rpc) this.uiAttached = true; + })().finally(() => { + this.refreshingUi = null; + }); + return this.refreshingUi; + } + + remove(websocket) { + this.clients.delete(websocket); + if (this.clients.size > 0) return; + this.stopped = true; + if (this.resizeTimer) clearTimeout(this.resizeTimer); + this.rpc?.close(); + this.onEmpty(); + } + + scheduleResize(columns, rows) { + this.pendingResize = { columns, rows }; + if (this.resizeTimer) clearTimeout(this.resizeTimer); + this.resizeTimer = setTimeout(() => { + this.resizeTimer = null; + void this.applyResize().catch(() => undefined); + }, 90); + } + + async resizeToTerminal() { + if (this.resizeTimer) { + clearTimeout(this.resizeTimer); + this.resizeTimer = null; + } + this.pendingResize = { + columns: this.uiSize.width, + rows: this.uiSize.height + }; + await this.applyResize(); + return this.uiSize; + } + + async applyResize() { + const requested = this.pendingResize; + const rpc = this.rpc; + this.pendingResize = null; + if (!requested || !rpc || this.stopped) return; + const uis = await rpc.call('nvim_list_uis'); + if (rpc !== this.rpc) return; + const terminalUi = Array.isArray(uis) + ? uis.find(ui => ui?.stdin_tty && ui?.stdout_tty) + : null; + const columns = Number.isInteger(terminalUi?.width) + ? terminalUi.width + : requested.columns; + const rows = Number.isInteger(terminalUi?.height) + ? terminalUi.height + : requested.rows; + const size = `${columns}x${rows}`; + if (size === this.appliedUiSize) return; + await rpc.call('nvim_ui_try_resize', [columns, rows]); + if (rpc !== this.rpc) return; + await rpc.call('nvim_command', ['silent! StudentLayoutFit']); + this.uiSize = { width: columns, height: rows }; + this.appliedUiSize = size; + } + + handleMessage(payload) { + if (!this.rpc || this.stopped) return; + try { + const message = JSON.parse(payload.toString('utf8')); + if (message?.type === 'refresh') { + void this.resizeToTerminal() + .then(() => this.refreshUi()) + .catch(() => undefined); + return; + } + if (message?.type === 'input' && typeof message.keys === 'string' && message.keys.length <= 256) { + void this.rpc.call('nvim_input', [message.keys]).catch(() => undefined); + return; + } + if (message?.type !== 'mouse') return; + const button = ['left', 'middle', 'right', 'wheel'].includes(message.button) + ? message.button + : null; + const action = ['press', 'drag', 'release', 'up', 'down'].includes(message.action) + ? message.action + : null; + const row = Number(message.row); + const column = Number(message.column); + if (!button || !action || !Number.isInteger(row) || !Number.isInteger(column)) return; + void this.rpc.call('nvim_input_mouse', [ + button, + action, + typeof message.modifier === 'string' ? message.modifier.slice(0, 16) : '', + 0, + Math.max(0, row), + Math.max(0, column) + ]).catch(() => undefined); + } catch { + // Only the typed input/mouse/refresh capabilities above are exposed. + } + } + + async run() { + while (!this.stopped && this.clients.size > 0) { + let selected; + try { + selected = await selectedTarget(this.selectionRoot); + } catch (error) { + this.broadcast({ + type: 'status', + state: 'unselected', + message: `Brak aktywnego celu MCP: ${errorText(error)}` + }); + await delay(RETRY_DELAY_MS); + continue; + } + + this.broadcast({ + type: 'status', + state: 'connecting', + epoch: selected.epoch, + target: selected.metadata + }); + + let targetTimer = null; + try { + this.rpc = new NvimRpcClient(selected.socketPath, (method, parameters) => { + if (method !== 'redraw') return; + this.broadcast({ + type: 'redraw', + epoch: selected.epoch, + events: parameters + }); + }); + await this.rpc.connect(); + this.appliedUiSize = ''; + this.uiAttached = false; + void this.rpc.readLoop.catch(() => undefined); + const uis = await this.rpc.call('nvim_list_uis'); + const size = existingUiSize(uis); + this.uiSize = size; + await this.rpc.call('nvim_set_client_info', [ + 'stem-card-browser', + { major: 0, minor: 1, patch: 0 }, + 'ui', + {}, + { website: 'local://stem-card', license: 'private' } + ]); + await this.rpc.call('nvim_ui_attach', [size.width, size.height, { + rgb: false, + ext_linegrid: true, + ext_multigrid: false, + override: false + }]); + this.uiAttached = true; + this.broadcast({ + type: 'status', + state: 'connected', + epoch: selected.epoch, + target: selected.metadata, + grid: size, + control: 'typed-input' + }); + + const targetChanged = new Promise((_, reject) => { + targetTimer = setInterval(async () => { + try { + const current = await selectedTarget(this.selectionRoot); + if (current.epoch !== selected.epoch) { + reject(new Error('Zmieniono cel MCP.')); + } + } catch (error) { + reject(error); + } + }, TARGET_POLL_MS); + }); + await Promise.race([this.rpc.readLoop, targetChanged]); + } catch (error) { + if (!this.stopped) { + this.broadcast({ + type: 'status', + state: 'offline', + epoch: selected.epoch, + target: selected.metadata, + message: errorText(error) + }); + } + } finally { + if (targetTimer) clearInterval(targetTimer); + this.uiAttached = false; + this.rpc?.close(); + this.rpc = null; + } + if (!this.stopped) await delay(RETRY_DELAY_MS); + } + } +} + +const hubs = new Map(); + +function selectionRootFor(options = {}) { + return options.selectionRoot + ?? process.env.MCP_CONTAINER_SELECTION_ROOT + ?? path.join(process.env.XDG_RUNTIME_DIR ?? '/run/user/1000', 'stem/mcp-selected'); +} + +export async function callSelectedNvim(method, parameters = [], options = {}) { + const selectionRoot = selectionRootFor(options); + const hub = hubs.get(selectionRoot); + const pinnedSocketPath = options.socketPath + ? await realpath(options.socketPath) + : null; + if ( + hub?.rpc + && !hub.stopped + && (!pinnedSocketPath || hub.rpc.socketPath === pinnedSocketPath) + ) { + return hub.rpc.call(method, parameters); + } + const socketPath = pinnedSocketPath ?? (await selectedTarget(selectionRoot)).socketPath; + const rpc = new NvimRpcClient(socketPath, () => undefined); + await rpc.connect(); + void rpc.readLoop.catch(() => undefined); + try { + return await rpc.call(method, parameters); + } finally { + rpc.close(); + } +} + +export async function resizeSelectedNvimUi(options = {}) { + const selectionRoot = selectionRootFor(options); + const hub = hubs.get(selectionRoot); + if (!hub?.rpc || hub.stopped || !hub.uiAttached) { + return { attached: false, grid: null }; + } + const grid = await hub.resizeToTerminal(); + await hub.refreshUi(); + return { attached: true, grid }; +} + +export async function selectedNvimTarget(options = {}) { + return selectedTarget(selectionRootFor(options)); +} + +/** + * Attach a browser to the one shared external UI for the selected container. + * The browser never receives the Unix socket and cannot issue arbitrary RPC. + */ +export function attachNvimUi(websocket, options = {}) { + const selectionRoot = selectionRootFor(options); + let hub = hubs.get(selectionRoot); + if (!hub || hub.stopped) { + hub = new NvimUiHub(selectionRoot, () => hubs.delete(selectionRoot)); + hubs.set(selectionRoot, hub); + } + hub.add(websocket); +} diff --git a/scripts/render_card_layouts.sh b/scripts/render_card_layouts.sh new file mode 100755 index 0000000..cabe262 --- /dev/null +++ b/scripts/render_card_layouts.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env sh +set -eu + +script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd) +repo_root=$(CDPATH= cd "$script_dir/.." && pwd) + +if [ -n "${CARD_LAYOUTS_ROOT:-}" ]; then + layouts_root=$CARD_LAYOUTS_ROOT +else + layouts_root=$repo_root/../../../tools/card-layouts +fi + +renderer=$layouts_root/tools/render_card.py +if [ ! -f "$renderer" ]; then + echo "Nie znaleziono generatora card-layouts: $renderer" >&2 + echo "Ustaw CARD_LAYOUTS_ROOT na checkout edu-tools/card-layouts." >&2 + exit 1 +fi + +exec python3 "$renderer" "$repo_root" "$@" diff --git a/scripts/render_pdf.sh b/scripts/render_pdf.sh new file mode 100755 index 0000000..349d42c --- /dev/null +++ b/scripts/render_pdf.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env sh +set -eu + +DOC_DIR="doc" +TEX_FILE="${CARD_TEX_FILE:-generated/main.tex}" +METADATA_TEX_FILE="${CARD_PDF_METADATA_TEX_FILE:-main.tex}" + +script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd) +repo_root=$(CDPATH= cd "$script_dir/.." && pwd) +repo_name="${REPO_NAME:-$(basename "$repo_root")}" +tex_path="$repo_root/$DOC_DIR/$TEX_FILE" +metadata_tex_path="$repo_root/$DOC_DIR/$METADATA_TEX_FILE" +tex_dir=$(dirname "$tex_path") +tex_name=$(basename "$tex_path") + +read_tex_command() { + sed -n "s/^\\\\newcommand{\\\\$1}{\\([^}]*\\)}$/\\1/p" "$metadata_tex_path" | head -n 1 +} + +publisher_domain=$(read_tex_command PublisherDomain) +card_area=$(read_tex_command CardArea) +card_series=$(read_tex_command CardSeries) +card_number=$(read_tex_command CardNumber) +card_slug=$(read_tex_command CardSlug) +card_version=$(read_tex_command CardVersion) +document_uuid=$(read_tex_command DocumentUUID) + +if [ -z "$publisher_domain" ] || [ -z "$card_area" ] || [ -z "$card_series" ] || \ + [ -z "$card_number" ] || [ -z "$card_slug" ] || [ -z "$card_version" ] || \ + [ -z "$document_uuid" ]; then + echo "missing PDF filename metadata in $metadata_tex_path" >&2 + exit 1 +fi + +if [ ! -f "$tex_path" ]; then + echo "missing generated card TeX: $tex_path" >&2 + exit 1 +fi + +commit="${GITEA_SHA:-${GITHUB_SHA:-}}" +if [ -z "$commit" ]; then + commit=$(git -C "$repo_root" rev-parse HEAD) +fi +short_commit=$(printf '%s' "$commit" | cut -c1-12) + +meta_file="$tex_dir/build-meta.tex" +tmp_meta="$meta_file.tmp.$$" +cleanup() { + rm -f \ + "$meta_file" \ + "$tmp_meta" \ + "$tex_dir/${tex_name%.tex}.upa" \ + "$tex_dir/${tex_name%.tex}.upb" +} +trap cleanup EXIT HUP INT TERM + +printf '\\newcommand{\\BuildCommit}{%s}\n' "$short_commit" > "$tmp_meta" +mv "$tmp_meta" "$meta_file" + +pdf_out_dir="${PDF_OUT_DIR:-$repo_root/doc/pdf}" +mkdir -p "$pdf_out_dir" +out_dir=$(CDPATH= cd "$pdf_out_dir" && pwd) +pdf_basename="$publisher_domain-$card_area-$card_series-$card_number-$card_slug-$card_version-$document_uuid.pdf" +find "$out_dir" -maxdepth 1 -type f -name '*.pdf' -delete + +( + cd "$tex_dir" + latexmk -pdf -interaction=nonstopmode -halt-on-error -outdir="$out_dir" "$tex_name" +) + +source_pdf="$out_dir/${tex_name%.tex}.pdf" +target_pdf="$out_dir/$pdf_basename" +if [ "$source_pdf" != "$target_pdf" ]; then + mv "$source_pdf" "$target_pdf" +fi + +find "$out_dir" -maxdepth 1 -type f \( \ + -name '*.aux' -o \ + -name '*.log' -o \ + -name '*.out' -o \ + -name '*.fls' -o \ + -name '*.fdb_latexmk' -o \ + -name '*.upa' -o \ + -name '*.upb' \ +\) -delete diff --git a/src/tasks/task01_pin_polarity.sv b/src/tasks/task01_pin_polarity.sv new file mode 100644 index 0000000..b487fce --- /dev/null +++ b/src/tasks/task01_pin_polarity.sv @@ -0,0 +1,3 @@ +module pin_polarity(input logic reset_n, input logic button_n, input logic led_on, output logic reset, output logic button, output logic led_n); + assign reset = ~reset_n; assign button = ~button_n; assign led_n = ~led_on; +endmodule diff --git a/src/tasks/task02_bringup_blinker.sv b/src/tasks/task02_bringup_blinker.sv new file mode 100644 index 0000000..3d379ce --- /dev/null +++ b/src/tasks/task02_bringup_blinker.sv @@ -0,0 +1,7 @@ +module bringup_blinker #(parameter integer DIVIDER=3)(input logic clk, input logic reset_n, output logic led, output logic [3:0] counter); + always_ff @(posedge clk) begin + if (!reset_n) begin counter <= 0; led <= 0; end + else if (counter == DIVIDER) begin counter <= 0; led <= ~led; end + else counter <= counter + 1'b1; + end +endmodule diff --git a/src/tasks/task03_programming_gate.sv b/src/tasks/task03_programming_gate.sv new file mode 100644 index 0000000..f6d5b3f --- /dev/null +++ b/src/tasks/task03_programming_gate.sv @@ -0,0 +1,13 @@ +module programming_gate(input logic clk, input logic reset, input logic cable_ok, input logic id_ok, input logic configure_ok, input logic observe_ok, output logic [2:0] state, output logic evidence_ready); + always_ff @(posedge clk) begin + if (reset) state <= 0; + else case (state) + 0: if (cable_ok) state <= 1; + 1: if (id_ok) state <= 2; + 2: if (configure_ok) state <= 3; + 3: if (observe_ok) state <= 4; + default: state <= state; + endcase + end + assign evidence_ready = state == 4; +endmodule diff --git a/stem-card.yaml b/stem-card.yaml new file mode 100644 index 0000000..34c0c2a --- /dev/null +++ b/stem-card.yaml @@ -0,0 +1,10 @@ +schema: 1 +targets: + native: {profile: native-amd64, actions: [build, test, run]} + hazard3-baremetal: {profile: hazard3-sim, actions: [build, test, run, debug]} +actions: + build: [bash, tools/card-action.sh, build] + test: [bash, tools/card-action.sh, test] + run: [bash, tools/card-action.sh, run] + debug: [bash, tools/card-action.sh, debug] +artifacts: {directory: .stem/artifacts} diff --git a/tests/card_contract.test.mjs b/tests/card_contract.test.mjs new file mode 100644 index 0000000..8eaa6d0 --- /dev/null +++ b/tests/card_contract.test.mjs @@ -0,0 +1,5 @@ +import assert from 'node:assert/strict'; import { access, readFile } from 'node:fs/promises'; import test from 'node:test'; +const source=JSON.parse(await readFile(new URL('../json/card_source.json',import.meta.url),'utf8')); +test('L02 identity and three tasks are stable',()=>{assert.equal(source.card.series,'rv32i-asm');assert.equal(source.card.number,'02');assert.equal(source.card.uuid,'31e9c2ad-f597-51ab-af6a-e550c3533f32');assert.deepEqual(source.tasks_order,['task01','task02','task03']);}); +test('simulation never claims physical FPGA success',()=>{const text=JSON.stringify(source);for(const token of ['hardware_observed=0','preflight-only','board-contract.yaml','bitstream','device ID','VCD'])assert.match(text,new RegExp(token,'i'));assert.match(text,/nie jest dowodem/i);}); +test('all task sources and board template exist',async()=>{for(const name of ['task01_pin_polarity','task02_bringup_blinker','task03_programming_gate'])await access(new URL(`../src/tasks/${name}.sv`,import.meta.url));await access(new URL('../board/board-contract.yaml',import.meta.url));}); diff --git a/tests/card_state_db.test.mjs b/tests/card_state_db.test.mjs new file mode 100644 index 0000000..be502eb --- /dev/null +++ b/tests/card_state_db.test.mjs @@ -0,0 +1,67 @@ +import assert from 'node:assert/strict'; +import { mkdtemp, rm } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; +import { CardStateDatabase } from '../scripts/lib/card_state_db.mjs'; + +test('card state survives closing and reopening SQLite', async () => { + const directory = await mkdtemp(path.join(os.tmpdir(), 'stem-card-state-')); + const file = path.join(directory, 'state.sqlite3'); + const state = { + schema: 'stem-card-navigation.v2', + revision: 7, + selected_at: '2026-07-17T12:00:00.000Z', + snapshot_ref: 'task04.alloc5.cursor' + }; + + try { + const first = new CardStateDatabase(file); + first.write('navigation', state); + first.close(); + + const second = new CardStateDatabase(file); + assert.deepEqual(second.read('navigation'), state); + assert.equal(second.read('missing'), null); + second.close(); + } finally { + await rm(directory, { recursive: true, force: true }); + } +}); + +test('audit log is append-only, ordered and keeps navigation timestamps', async () => { + const directory = await mkdtemp(path.join(os.tmpdir(), 'stem-card-audit-')); + const file = path.join(directory, 'state.sqlite3'); + try { + const database = new CardStateDatabase(file); + const navigation = { + task: { id: 'task04' }, + block: { id: 'allocator-flow' }, + phase: { id: 'alloc5' }, + step: { id: 'alloc5-commit' }, + snapshot_ref: 'task04.alloc5.commit' + }; + const first = database.appendEvent({ + eventType: 'navigation.activate', + actor: 'nvim', + navigation, + occurredAt: '2026-07-17T12:00:00.000Z', + payload: { source: 'F2' } + }); + const second = database.appendEvent({ + eventType: 'checkpoint.ready', + actor: 'nvim', + navigation, + occurredAt: '2026-07-17T12:00:01.000Z' + }); + assert.equal(second.id, first.id + 1); + assert.deepEqual( + database.listEvents({ afterId: first.id, limit: 10 }).map(event => event.event_type), + ['checkpoint.ready'] + ); + assert.equal(database.listEvents()[0].snapshot_ref, navigation.snapshot_ref); + database.close(); + } finally { + await rm(directory, { recursive: true, force: true }); + } +}); diff --git a/tests/reference_model.py b/tests/reference_model.py new file mode 100755 index 0000000..d2f4f1a --- /dev/null +++ b/tests/reference_model.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +"""Independent preflight models for L02; they never claim physical FPGA execution.""" +import sys + + +def task01(): + reset_n, button_n, led_on = 0, 0, 1 + assert (not reset_n, not button_n, not led_on) == (True, True, False) + print("PASS task01 reset_active=1 button_active=1 led_n=0 pin_contract=generic") + + +def task02(): + counter = 0; led = 0; edges = [] + for cycle in range(1, 17): + if counter == 3: counter = 0; led ^= 1; edges.append(cycle) + else: counter += 1 + assert edges == [4, 8, 12, 16] + print("PASS task02 reset_known=1 led_edges=4,8,12,16 divider=4") + + +def task03(): + states = ["idle", "cable", "identified", "configured", "observed"] + assert states == ["idle", "cable", "identified", "configured", "observed"] + print("PASS task03 order=cable,id,configure,observe workflow_model=complete hardware_observed=0 claim=preflight-only") + + +TASKS = {"task01_pin_polarity": task01, "task02_bringup_blinker": task02, "task03_programming_gate": task03} +if len(sys.argv) != 2 or sys.argv[1] not in TASKS: raise SystemExit(f"usage: {sys.argv[0]} {'|'.join(TASKS)}") +TASKS[sys.argv[1]]() diff --git a/tests/task01_pin_polarity_tb.sv b/tests/task01_pin_polarity_tb.sv new file mode 100644 index 0000000..c92b0fc --- /dev/null +++ b/tests/task01_pin_polarity_tb.sv @@ -0,0 +1,4 @@ +module tb; + logic reset_n, button_n, led_on, reset, button, led_n; string trace_file; pin_polarity dut(.*); + initial begin if ($value$plusargs("trace=%s",trace_file)) begin $dumpfile(trace_file);$dumpvars(0,tb);end reset_n=0;button_n=0;led_on=1;#1;if(!reset||!button||led_n)$fatal(1,"active-low mismatch");reset_n=1;button_n=1;led_on=0;#1;if(reset||button||!led_n)$fatal(1,"inactive mismatch");$display("PASS task01 reset_active=1 button_active=1 led_n=0 pin_contract=generic");$finish;end +endmodule diff --git a/tests/task02_bringup_blinker_tb.sv b/tests/task02_bringup_blinker_tb.sv new file mode 100644 index 0000000..99c0e04 --- /dev/null +++ b/tests/task02_bringup_blinker_tb.sv @@ -0,0 +1,4 @@ +module tb; + logic clk=0,reset_n,led;logic[3:0]counter;integer cycle;integer edges;logic previous;string trace_file;bringup_blinker dut(.*);always #5 clk=~clk; + initial begin if($value$plusargs("trace=%s",trace_file))begin $dumpfile(trace_file);$dumpvars(0,tb);end reset_n=0;edges=0;previous=0;@(posedge clk);#1;if(counter!==0||led!==0)$fatal(1,"reset mismatch");reset_n=1;for(cycle=1;cycle<=16;cycle=cycle+1)begin @(posedge clk);#1;if(led!=previous)begin edges=edges+1;if(cycle%4!=0)$fatal(1,"edge cycle mismatch");end previous=led;end if(edges!=4)$fatal(1,"edge count mismatch");$display("PASS task02 reset_known=1 led_edges=4,8,12,16 divider=4");$finish;end +endmodule diff --git a/tests/task03_programming_gate_tb.sv b/tests/task03_programming_gate_tb.sv new file mode 100644 index 0000000..11ce0b7 --- /dev/null +++ b/tests/task03_programming_gate_tb.sv @@ -0,0 +1,5 @@ +module tb; + logic clk=0,reset,cable_ok,id_ok,configure_ok,observe_ok,evidence_ready;logic[2:0]state;string trace_file;programming_gate dut(.*);always #5 clk=~clk; + task step; input logic c,i,p,o; begin cable_ok=c;id_ok=i;configure_ok=p;observe_ok=o;@(posedge clk);#1;end endtask + initial begin if($value$plusargs("trace=%s",trace_file))begin $dumpfile(trace_file);$dumpvars(0,tb);end reset=1;cable_ok=0;id_ok=0;configure_ok=0;observe_ok=0;@(posedge clk);#1;reset=0;step(0,0,1,1);if(state!=0)$fatal(1,"gate skipped cable");step(1,0,0,0);if(state!=1)$fatal(1,"cable");step(0,1,0,0);if(state!=2)$fatal(1,"id");step(0,0,1,0);if(state!=3)$fatal(1,"configure");step(0,0,0,1);if(state!=4||!evidence_ready)$fatal(1,"observe");$display("PASS task03 order=cable,id,configure,observe workflow_model=complete hardware_observed=0 claim=preflight-only");$finish;end +endmodule diff --git a/tests/test_rtl.sh b/tests/test_rtl.sh new file mode 100755 index 0000000..4aaca8d --- /dev/null +++ b/tests/test_rtl.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail +root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"; mode=test +if [[ "${1:-}" == --build-only || "${1:-}" == --trace ]]; then mode="${1#--}"; shift; fi +task="${1:?task required}"; case "$task" in task01_pin_polarity|task02_bringup_blinker|task03_programming_gate) ;; *) exit 2 ;; esac +state="${STEM_STATE_DIR:-$root/.stem/instances/local}"; profile="${STEM_PROFILE:-hazard3-sim}"; target="${STEM_TARGET:-hazard3-baremetal}"; build_dir="$state/build/rtl/$task"; artifact_dir="$root/.stem/artifacts/$profile/$target/$task"; binary="$build_dir/sim"; mkdir -p "$build_dir" "$artifact_dir" +args=(--binary --timing --assert -Wall -Wno-fatal -Wno-DECLFILENAME -Wno-BLKSEQ -Wno-UNUSEDSIGNAL -Wno-WIDTH --top-module tb --Mdir "$build_dir/obj" -o "$binary" "$root/src/tasks/$task.sv" "$root/tests/${task}_tb.sv"); [[ "$mode" == trace ]] && args+=(--trace) +verilator "${args[@]}" >/dev/null; cp -f "$root/src/tasks/$task.sv" "$artifact_dir/" +[[ "$mode" == build-only ]] && { printf 'BUILD %s binary=%s\n' "$task" "$binary"; exit 0; } +if [[ "$mode" == trace ]]; then trace="$artifact_dir/$task.vcd"; "$binary" "+trace=$trace"; test -s "$trace"; printf 'TRACE %s vcd=%s\n' "$task" "$trace"; else "$binary"; fi diff --git a/tests/validate_board_contract.py b/tests/validate_board_contract.py new file mode 100755 index 0000000..8d9858f --- /dev/null +++ b/tests/validate_board_contract.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 +"""Validate that the repository carries an explicit, unclaimed board template.""" +import sys +from pathlib import Path +text = Path(sys.argv[1]).read_text() +required = ["status: template", "board_id: SET_BOARD_ID", "clock_pin: SET_CLOCK_PIN", "led_pin: SET_LED_PIN", "hardware_observed: false"] +missing = [item for item in required if item not in text] +if missing: raise SystemExit("invalid board contract: " + ", ".join(missing)) +print("BOARD-CONTRACT status=template hardware_observed=0") diff --git a/tools/card-action.sh b/tools/card-action.sh new file mode 100755 index 0000000..2a49949 --- /dev/null +++ b/tools/card-action.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail +action="${1:?action required}"; root="${STEM_REPO:-$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)}"; profile="${STEM_PROFILE:-native-amd64}"; target="${STEM_TARGET:-native}"; selector="${STEM_TASK:-task1}" +tasks=(task01_pin_polarity task02_bringup_blinker task03_programming_gate) +resolve_task(){ local value="${1,,}" name; for name in "${tasks[@]}"; do [[ "$value" == "$name" ]] && { printf '%s\n' "$name"; return; }; done; [[ "$value" =~ ^(task|t)?[-_]?0*([1-3])($|[-_].*) ]] && { printf '%s\n' "${tasks[${BASH_REMATCH[2]} - 1]}"; return; }; exit 2; } +task="$(resolve_task "$selector")" +case "$profile:$target:$action" in + native-amd64:native:build) python3 -m py_compile "$root/tests/reference_model.py"; python3 "$root/tests/validate_board_contract.py" "$root/board/board-contract.yaml" ;; + native-amd64:native:test|native-amd64:native:run) exec python3 "$root/tests/reference_model.py" "$task" ;; + hazard3-sim:hazard3-baremetal:build) exec "$root/tests/test_rtl.sh" --build-only "$task" ;; + hazard3-sim:hazard3-baremetal:test|hazard3-sim:hazard3-baremetal:run) exec "$root/tests/test_rtl.sh" "$task" ;; + hazard3-sim:hazard3-baremetal:debug) exec "$root/tests/test_rtl.sh" --trace "$task" ;; + *) printf 'Unsupported profile/target/action: %s/%s/%s\n' "$profile" "$target" "$action" >&2; exit 2 ;; +esac diff --git a/web/app.css b/web/app.css new file mode 100644 index 0000000..388a0ab --- /dev/null +++ b/web/app.css @@ -0,0 +1 @@ +.paper{display:flex;flex-direction:column;gap:3.3cm}.paper>.sheet,.paper>.sheet-spread>.sheet{width:210mm;height:297mm;min-height:297mm;max-height:297mm;margin:0 auto;overflow:hidden}.paper>.sheet.sheet--landscape,.paper>.sheet-spread>.sheet.sheet--landscape{width:297mm;min-width:297mm;max-width:297mm;height:210mm;min-height:210mm;max-height:210mm}.paper>.sheet>.sheet-content,.paper>.sheet-spread>.sheet>.sheet-content{box-sizing:border-box;height:297mm;min-height:297mm;max-height:297mm;overflow-x:hidden;overflow-y:hidden}.paper>.sheet.sheet--landscape>.sheet-content,.paper>.sheet-spread>.sheet.sheet--landscape>.sheet-content{height:210mm;min-height:210mm;max-height:210mm}.page-resource-footer{position:absolute;z-index:4;right:21.5mm;bottom:3mm;left:21.5mm;display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);align-items:center;gap:8px;min-height:5mm;border-top:.35mm solid #252525;background:#fff;padding-top:1.2mm;color:#68747a;font:650 7px/1.15 ui-monospace,monospace}.page-resource-footer__uuid{overflow:hidden;color:#4d5d65;text-align:left;text-overflow:ellipsis;white-space:nowrap;font:inherit}.page-resource-footer__author{color:#4d5d65;text-align:center;white-space:nowrap}.page-resource-footer__page{color:#283940;text-align:right;white-space:nowrap}.sheet-spread{display:contents}.paper.is-diagram-spread .sheet-spread{display:grid;grid-template-columns:repeat(var(--spread-columns, 1),max-content);width:max-content;max-width:none;margin:0 auto;align-items:flex-start;gap:2mm;zoom:var(--spread-fit-scale, 1)}.paper.is-diagram-spread .sheet-spread>.sheet{flex:0 0 auto}.viewer-chrome{position:sticky;top:0;z-index:80;width:100%}.viewer-action-toast{position:absolute;z-index:12;right:9px;bottom:9px;max-width:min(720px,calc(100vw - 18px));box-sizing:border-box;overflow:hidden;border:1px solid #42b8e7;border-radius:3px;background:#102c38ee;color:#dff7ff;padding:5px 9px;box-shadow:0 4px 14px #0007;text-overflow:ellipsis;white-space:nowrap;font:750 10px/1.25 ui-monospace,monospace}.viewer-chrome .topbar{position:sticky;top:0;z-index:90}.viewpoint-bar{display:grid;grid-template-columns:repeat(8,minmax(0,1fr));gap:1px;min-height:31px;border-top:1px solid #9aa8ae;border-bottom:1px solid #718087;background:#718087;box-shadow:0 2px 5px #0002}.viewpoint-bar button{position:relative;display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:5px;min-width:0;border:0;background:#f7f9fa;color:#1f3038;padding:5px 7px;text-align:left;cursor:pointer}.viewpoint-bar button:hover,.viewpoint-bar button:focus-visible{z-index:1;background:#e9f3f7;outline:2px solid #287495;outline-offset:-2px}.viewpoint-bar button[aria-current=location]{background:#dcecf3;box-shadow:inset 0 -3px #167ca4}.viewpoint-bar button>span{color:#1b6785;font:850 10px/1 ui-monospace,monospace}.viewpoint-bar button>strong{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:750 10px/1.1 system-ui,sans-serif}.viewpoint-bar button.is-unavailable{background:#eef0f1;color:#5d686d;cursor:not-allowed}.viewpoint-bar button.is-unavailable>span{color:#68747a}.viewpoint-bar button.is-unavailable>small{border:1px solid #9da7ab;border-radius:2px;padding:1px 3px;font:750 7px/1 ui-monospace,monospace}.card-library-toggle{display:inline-flex;align-items:center;flex:none;height:28px;gap:6px;border:1px solid #6e7d84;border-radius:3px;background:#f6f8f9;color:#26373f;padding:2px 8px;font:750 11px/1 ui-monospace,monospace;cursor:pointer}.card-library-toggle:hover,.card-library-toggle:focus-visible{border-color:#1d7799;background:#edf7fa;outline:none}.card-library-toggle.is-active{border-color:#126d8f;background:#174f67;color:#fff}.card-library-toggle-icon{font:700 16px/1 system-ui,sans-serif}.card-library-toggle kbd{color:#267697;font:inherit}.card-library-toggle.is-active kbd{color:#aee9ff}.runtime-source-selector{display:inline-flex;align-items:center;flex:none;height:28px;box-sizing:border-box;gap:5px;border:1px solid #6e7d84;border-radius:3px;background:#edf2f4;color:#44545b;padding-left:7px;font:750 9px/1 ui-monospace,monospace;letter-spacing:.05em}.runtime-source-selector select{height:26px;min-width:17ch;max-width:19ch;border:0;border-left:1px solid #a8b4b9;border-radius:0 2px 2px 0;background:#fff;color:#174f67;padding:2px 22px 2px 7px;font:800 10px/1 ui-monospace,monospace;cursor:pointer}.runtime-source-selector select:hover,.runtime-source-selector select:focus-visible{background:#edf7fa;outline:2px solid #287495;outline-offset:-2px}.runtime-source-selector option:disabled{color:#7d898e}:root{--side-panel-top: 36px;--side-panel-width: min(960px, calc(100vw - 28px) );--side-panel-trigger-length: 56px}.side-panel-dock{position:fixed;z-index:240;top:var(--side-panel-top);bottom:0;width:0;pointer-events:none}.side-panel-dock--left{left:0}.side-panel-dock--right{right:0}.side-panel-rail{position:fixed;z-index:243;top:calc(var(--side-panel-top) + 8px);display:flex;width:13px;flex-direction:column;gap:10px;pointer-events:auto}.side-panel-dock--left .side-panel-rail{left:0}.side-panel-dock--right .side-panel-rail{right:0}.side-panel-trigger{--side-panel-accent: #167ca4;position:relative;display:grid;width:13px;height:var(--side-panel-trigger-length);place-items:center;border:0;border-radius:0;background:transparent;padding:0;cursor:pointer}.side-panel-trigger:before{content:"";display:block;width:3px;height:calc(var(--side-panel-trigger-length) - 8px);border-radius:999px;background:var(--side-panel-accent);box-shadow:0 0 0 1px #ffffff73,0 3px 10px -5px var(--side-panel-accent);opacity:.78;transition:width .14s ease,opacity .14s ease,box-shadow .14s ease}.side-panel-trigger:hover:before,.side-panel-trigger:focus-visible:before,.side-panel-trigger.is-open:before{width:4px;opacity:1}.side-panel-trigger.is-pinned:before{width:5px;box-shadow:0 0 0 1px #ffffff9e,0 3px 12px -4px var(--side-panel-accent)}.side-panel-trigger:focus-visible{outline:2px solid var(--side-panel-accent);outline-offset:-2px}.side-panel-trigger>span{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}.side-panel{position:fixed;z-index:241;top:var(--side-panel-top);bottom:0;display:flex;width:var(--side-panel-width);max-width:calc(100vw - 20px);box-sizing:border-box;flex-direction:column;overflow:hidden;border-color:#819098;background:#f7f9fa;color:#17252c;pointer-events:none;transition:transform .18s cubic-bezier(.22,.78,.24,1);will-change:transform}.side-panel-dock--left .side-panel{left:0;border-right:1px solid #819098;box-shadow:15px 0 34px -24px #101b218c;transform:translate(calc(-100% - 14px))}.side-panel-dock--right .side-panel{right:0;border-left:1px solid #819098;box-shadow:-15px 0 34px -24px #101b218c;transform:translate(calc(100% + 14px))}.side-panel.is-open{transform:translate(0);pointer-events:auto}.side-panel:after{content:"";position:absolute;top:10px;width:4px;height:48px;border-radius:999px;background:var(--side-panel-accent);pointer-events:none}.side-panel-dock--left .side-panel:after{right:-2px}.side-panel-dock--right .side-panel:after{left:-2px}.side-panel-header{display:flex;min-height:38px;box-sizing:border-box;align-items:center;justify-content:space-between;gap:10px;border-bottom:1px solid #aebbc1;background:#e7edef;padding:4px 7px 4px 10px}.side-panel-header>div:first-child{display:flex;min-width:0;align-items:baseline;gap:7px}.side-panel-header small{color:#52666f;font:600 16px/1 ui-monospace,monospace;letter-spacing:.08em}.side-panel-header strong{overflow:hidden;color:#11191d;text-overflow:ellipsis;white-space:nowrap;font:600 16px/1.2 system-ui,sans-serif}.side-panel-actions{display:flex;flex:none;gap:4px}.side-panel-actions button{display:grid;min-width:38px;height:24px;place-items:center;border:1px solid #8b999f;border-radius:3px;background:#fff;color:#26373f;padding:0 6px;font:650 16px/1 ui-monospace,monospace;cursor:pointer}.side-panel-actions button:last-child{min-width:24px;padding:0;font-size:18px}.side-panel-actions button:hover,.side-panel-actions button:focus-visible,.side-panel-actions button.is-pinned{border-color:var(--side-panel-accent);background:#edf7fa;outline:none}.side-panel-body,.card-library-panel{display:flex;min-height:0;flex:1;flex-direction:column;overflow:hidden}.card-library-header{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;padding:18px 18px 15px;border-bottom:1px solid #bdc9ce;background:#e8eef1}.card-library-header small{color:#52666f;font:600 16px/1.25 ui-monospace,monospace;letter-spacing:.09em;text-transform:uppercase}.card-library-header h2{margin:4px 0 2px;font:750 22px/1.15 system-ui,sans-serif}.card-library-header p{margin:0;color:#607078;font:500 16px/1.35 system-ui,sans-serif}.card-library-header>button{display:grid;width:34px;height:34px;place-items:center;flex:none;border:1px solid #77878e;border-radius:3px;background:#fff;color:#25353c;padding:0;font:25px/1 system-ui,sans-serif;cursor:pointer}.card-library-header>button:hover,.card-library-header>button:focus-visible{border-color:#126d8f;background:#edf7fa;outline:2px solid #85c5dc;outline-offset:1px}.card-library-tree{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:6px 0 18px;scrollbar-color:#8a9aa1 #edf1f3}.card-library-tree details{border:0}.card-library-tree summary{display:grid;grid-template-columns:14px minmax(0,1fr) auto;align-items:center;gap:6px;min-height:30px;box-sizing:border-box;padding:5px 10px 5px 4px;list-style:none;cursor:pointer;user-select:none}.card-library-tree summary::-webkit-details-marker{display:none}.card-library-tree summary:hover,.card-library-tree summary:focus-visible{background:#eaf1f4;outline:none}.card-library-branch{color:#25363d;font:16px/1 ui-monospace,monospace;transform:rotate(-90deg);transition:transform .12s ease}.card-library-tree details[open]>summary .card-library-branch{transform:rotate(0)}.card-library-tree summary strong,.card-library-tree summary small{display:block}.card-library-tree summary strong{color:#070a0b;font:550 16px/1.3 system-ui,sans-serif}.card-library-tree summary small{margin-top:2px;color:#303b40;font:500 16px/1.25 ui-monospace,monospace;letter-spacing:.04em}.card-library-tree summary output{min-width:22px;color:#26363d;padding:0 2px;text-align:center;font:550 16px/1.2 ui-monospace,monospace}.card-library-subject>summary{min-height:42px;border-bottom:1px solid #9eacb2;background:#dfe8ec;padding:7px 14px}.card-library-subject>summary:hover,.card-library-subject>summary:focus-visible{background:#d5e1e5}.card-library-subject>summary strong{color:#000;font:600 16px/1.3 system-ui,sans-serif}.card-library-subject>summary small{color:#28353a;font:500 16px/1.25 ui-monospace,monospace}.card-library-level-list{margin-left:18px;border-left:1px solid #8e9da4}.card-library-level>summary{min-height:38px;border-bottom:1px solid #cbd4d8;padding-left:8px}.card-library-level>summary strong{color:#000;font:550 16px/1.3 system-ui,sans-serif}.card-library-series-list{margin-left:18px;border-left:1px solid #a5b1b6;padding:2px 0 6px}.card-library-series>summary{min-height:34px;padding-left:8px}.card-library-series>summary strong{color:#000;font:550 16px/1.3 system-ui,sans-serif}.card-library-series>summary small{color:#26343a;font:500 16px/1.25 ui-monospace,monospace}.card-library-cards,.card-library-tasks{list-style:none;margin-top:0;margin-bottom:2px;padding:0}.card-library-cards{margin-left:18px;border-left:1px solid #bac3c7}.card-library-card-entry{margin:0}.card-library-card{display:grid;grid-template-columns:19px minmax(0,1fr) auto;align-items:start;gap:5px;min-height:38px;box-sizing:border-box;border:0;border-left:3px solid transparent;color:#000;padding:5px 8px 5px 3px;text-decoration:none}.card-library-node{color:#536168;font:16px/1.35 ui-monospace,monospace}.card-library-card-copy,.card-library-card-copy strong,.card-library-card-copy>span{display:block;min-width:0}.card-library-card-copy strong{color:#000;font:550 16px/1.3 ui-monospace,monospace}.card-library-card-copy>span{margin-top:2px;color:#0b0d0e;font:500 16px/1.35 system-ui,sans-serif;white-space:normal}.card-library-card-meta{display:flex;align-items:flex-end;flex-direction:column;gap:2px}.card-library-card-meta small{color:#26343a;font:500 16px/1.25 ui-monospace,monospace}.card-library-card.is-available{cursor:pointer}.card-library-card.is-available:hover,.card-library-card.is-available:focus-visible{background:#eaf5f8;outline:none}.card-library-card.is-current{border-left-color:#16769b;background:#dff1f7}.card-library-card.is-current:hover,.card-library-card.is-current:focus-visible{background:#cfeaf3;outline:1px solid #5ba9c4;outline-offset:-1px}.card-library-card.is-unavailable{color:#000}.card-library-card.is-unavailable .card-library-card-copy strong,.card-library-card.is-unavailable .card-library-card-meta small{color:#1d292e}.card-library-card.is-unavailable .card-library-card-copy>span{color:#0b0d0e}.card-library-tasks{margin-left:36px;border-left:1px solid #c3cacc}.card-library-tasks li>a,.card-library-tasks li>span{display:grid;grid-template-columns:19px auto minmax(0,1fr);gap:5px;align-items:start;color:#000;padding:4px 8px 5px 3px;text-decoration:none}.card-library-tasks li>a:hover,.card-library-tasks li>a:focus-visible{background:#eaf5f8;outline:none}.card-library-tasks strong{color:#000;font:550 16px/1.35 ui-monospace,monospace}.card-library-tasks li>a>span:last-child,.card-library-tasks li>span>span:last-child{color:#111;font:500 16px/1.35 system-ui,sans-serif}.card-library-footer{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:10px 16px;border-top:1px solid #bdc9ce;background:#edf1f3;color:#5a6a72;font:500 16px/1.3 system-ui,sans-serif}.card-library-footer span{display:inline-flex;align-items:center;gap:4px}.card-library-footer i{width:7px;height:7px;border-radius:50%;background:#929da2}.card-library-footer i.is-online{background:#26815b}.card-library-footer kbd{margin-left:auto;border:1px solid #a2afb5;border-radius:2px;background:#fff;padding:2px 5px;font:600 16px/1 ui-monospace,monospace}.card-library-treegrid{min-height:0;flex:1;overflow:auto;margin:8px;border:1px solid rgb(31 35 40 / 18%);border-radius:6px;background:#fff;scrollbar-color:#93a2a8 #edf1f2;scrollbar-width:thin}.card-library-treegrid-row{display:grid;min-width:760px;min-height:38px;box-sizing:border-box;grid-template-columns:minmax(500px,1fr) 110px 120px;align-items:stretch;border-bottom:1px solid rgb(31 35 40 / 9%);color:#273941;font:500 16px/1.25 ui-monospace,monospace}.card-library-treegrid-row>[role=gridcell],.card-library-treegrid-row>[role=columnheader]{display:flex;min-width:0;align-items:center;overflow:hidden;padding:7px 9px;text-overflow:ellipsis;white-space:nowrap}.card-library-treegrid-row>[role=gridcell]+[role=gridcell],.card-library-treegrid-row>[role=columnheader]+[role=columnheader]{border-left:1px solid rgb(31 35 40 / 9%)}.card-library-treegrid-head{position:sticky;z-index:3;top:0;min-height:34px;border-bottom-color:#1f232833;background:#eef1f2;color:#3e525b;font-weight:700;letter-spacing:.045em}.card-library-treegrid-row:not(.card-library-treegrid-head):hover{background:#1f23280a}.card-library-treegrid-row.is-subject{background:#dfe8e5}.card-library-treegrid-row.is-level{background:#f5f8f8}.card-library-treegrid-row.is-series{background:#f9fbfb}.card-library-treegrid-row.is-card{min-height:42px;background:#fff}.card-library-treegrid-row.is-task{background:#fbfcfc}.card-library-treegrid-row.is-current{box-shadow:inset 3px 0 #1680a5;background:#e1f1f6}.card-library-tree-title{gap:6px;padding-left:calc(8px + (var(--card-library-depth, 0) * 21px))!important;font-family:system-ui,sans-serif}.card-library-tree-expander{display:inline-grid;width:24px;height:24px;flex:none;place-items:center;border:0;background:transparent;color:#405961;padding:0;font:18px/1 ui-monospace,monospace;cursor:pointer}.card-library-tree-expander:hover,.card-library-tree-expander:focus-visible{background:#165d6b1a;color:#165d6b;outline:1px solid rgb(22 93 107 / 28%)}.card-library-tree-expander.is-empty{cursor:default}.card-library-tree-link,.card-library-tree-label{display:flex;min-width:0;align-items:baseline;gap:7px;overflow:hidden;color:#10181c;text-decoration:none;white-space:nowrap}.card-library-tree-link:hover,.card-library-tree-link:focus-visible{color:#075d7c;outline:none;text-decoration:underline;text-underline-offset:2px}.card-library-tree-link strong,.card-library-tree-label strong{flex:none;color:inherit;font:700 16px/1.25 ui-monospace,monospace}.card-library-tree-link>span,.card-library-tree-label>span{min-width:0;overflow:hidden;color:inherit;text-overflow:ellipsis;white-space:nowrap;font:500 16px/1.25 system-ui,sans-serif}.card-library-treegrid-row.is-subject .card-library-tree-label strong,.card-library-treegrid-row.is-level .card-library-tree-label strong,.card-library-treegrid-row.is-series .card-library-tree-label strong{font-family:system-ui,sans-serif;font-weight:650}.card-library-tree-state{display:inline-flex;min-width:76px;min-height:24px;box-sizing:border-box;align-items:center;justify-content:center;border:1px solid transparent;border-radius:5px;color:#33474f;padding:2px 7px;font:650 14px/1 ui-monospace,monospace;white-space:nowrap}.card-library-tree-state[data-state=current]{border-color:#3184a0;background:#e9f5f8;color:#075d7c}.card-library-tree-state[data-state=online]{border-color:#4b9a7a;background:#edf8f2;color:#176144}.card-library-tree-state[data-state=plan]{border-color:#c59a49;background:#fff7e5;color:#775313}.card-library-treegrid-row.is-unavailable .card-library-tree-label,.card-library-treegrid-row.is-unavailable .card-library-tree-label strong{color:#10181c}.side-toc,.side-progress{min-height:0;flex:1;overflow-y:auto;background:#fff;color:#11191d}.side-toc>header,.side-progress>header{padding:16px 18px 13px;border-bottom:1px solid #bdc9ce;background:#f0f4f5}.side-toc>header small,.side-progress>header small{color:#53666f;font:750 9px/1 ui-monospace,monospace;letter-spacing:.08em}.side-toc>header h2,.side-progress>header h2{margin:4px 0 0;color:#10181c;font:750 21px/1.15 system-ui,sans-serif}.side-toc ol{margin:0;padding:7px 0 20px;list-style:none}.side-toc li{border-bottom:1px solid #e2e7e9}.side-toc a{display:grid;grid-template-columns:34px minmax(0,1fr);gap:9px;align-items:baseline;padding:10px 16px;color:#11191d;text-decoration:none}.side-toc a:hover,.side-toc a:focus-visible{background:#eaf5f8;outline:none}.side-toc a span{color:#267493;font:800 10px/1 ui-monospace,monospace}.side-toc a strong{font:650 13px/1.3 system-ui,sans-serif}.side-progress-summary{display:flex;align-items:center;justify-content:space-between;gap:16px;margin:16px;border:1px solid #b6c2c7;background:#f4f7f8;padding:14px;font:650 13px/1.3 system-ui,sans-serif}.side-progress-summary strong{color:#1d617b;font:850 20px/1 ui-monospace,monospace}.side-progress-links{display:grid;gap:8px;padding:0 16px 18px}.side-progress-links a{border:1px solid #87979e;border-radius:3px;background:#fff;color:#174f67;padding:10px 12px;text-decoration:none;font:700 12px/1.2 system-ui,sans-serif}.side-progress-links a:hover,.side-progress-links a:focus-visible{border-color:#167ca4;background:#edf7fa;outline:none}.side-progress-error{margin:0 16px 14px;border-left:3px solid #a52d2d;background:#fff1f1;color:#7d1f1f;padding:8px 10px;font:12px/1.35 system-ui,sans-serif}.classroom-panel{display:flex;min-height:0;flex:1;flex-direction:column;overflow:hidden;background:#f7f9fa;color:#11191d}.classroom-treegrid{min-height:0;flex:1;overflow:auto;margin:8px;border:1px solid rgb(31 35 40 / 18%);border-radius:6px;background:#fff;scrollbar-color:#93a2a8 #edf1f2;scrollbar-width:thin}.classroom-treegrid-row{display:grid;min-width:920px;box-sizing:border-box;grid-template-columns:minmax(410px,1fr) 100px 150px 150px 110px;align-items:center;border-bottom:1px solid rgb(31 35 40 / 8%);color:#273941;font:500 16px/1.25 ui-monospace,monospace}.classroom-treegrid-row>[role=gridcell],.classroom-treegrid-row>[role=columnheader]{min-width:0;overflow:hidden;padding:8px;text-overflow:ellipsis;white-space:nowrap}.classroom-treegrid-row>[role=gridcell]+[role=gridcell],.classroom-treegrid-row>[role=columnheader]+[role=columnheader]{border-left:1px solid rgb(31 35 40 / 8%)}.classroom-treegrid-head{position:sticky;z-index:2;top:0;border-bottom-color:#1f23282e;background:#eef1f2;color:#3e525b;font-weight:650;letter-spacing:.04em}.classroom-node-row:hover{background:#1f23280a}.classroom-node-row.is-class{background:#dfe8e5}.classroom-node-row.is-student{background:#f8faf9}.classroom-node-row.is-task{background:#edf3f5}.classroom-node-row.is-block,.classroom-node-row.is-exercises{background:#f5f7f8}.classroom-node-row.is-phase{background:#fafbfb}.classroom-node-title{display:flex;align-items:center;gap:6px;padding-left:calc(8px + (var(--classroom-depth, 0) * 20px))!important;font-family:system-ui,sans-serif}.classroom-node-title strong{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:16px;font-weight:500}.classroom-node-row.is-class .classroom-node-title strong,.classroom-node-row.is-student .classroom-node-title strong,.classroom-node-row.is-task .classroom-node-title strong{font-weight:650}.classroom-expander{display:inline-grid;width:24px;height:24px;flex:none;place-items:center;border:0;background:transparent;color:#405961;padding:0;font:18px/1 ui-monospace,monospace;cursor:pointer}.classroom-expander:hover,.classroom-expander:focus-visible{background:#165d6b1a;color:#165d6b;outline:1px solid rgb(22 93 107 / 28%)}.classroom-expander.is-empty{cursor:default}.classroom-status,.classroom-sync,.classroom-visit,.classroom-commit{display:inline-flex;min-width:92px;box-sizing:border-box;align-items:center;justify-content:center;border:1px solid;border-radius:5px;padding:4px 6px;font:550 16px/1 ui-monospace,monospace;letter-spacing:.015em}.classroom-status[data-status=online]{border-color:#6b9a87;background:#e7f4ee;color:#155e43}.classroom-status[data-status=away]{border-color:#baa06a;background:#fff5d9;color:#765510}.classroom-status[data-status=offline]{border-color:#a5afb3;background:#f0f2f3;color:#596970}.classroom-status[data-status=error]{border-color:#bd7b7b;background:#fff0f0;color:#8c2929}.classroom-sync[data-sync=follow]{border-color:#6c98aa;background:#e8f3f7;color:#155c77}.classroom-sync[data-sync=paused]{border-color:#baa06a;background:#fff6df;color:#765510}.classroom-sync[data-sync=local]{border-color:#9a86ae;background:#f4eef9;color:#65427c}.classroom-visit[data-visit=present],.classroom-commit[data-commit=committed]{border-color:#6b9a87;background:#e7f4ee;color:#155e43}.classroom-visit[data-visit=missed],.classroom-commit[data-commit=missing]{border-color:#bd7b7b;background:#fff0f0;color:#8c2929}.classroom-visit[data-visit=pending],.classroom-commit[data-commit=working]{border-color:#baa06a;background:#fff6df;color:#765510}.classroom-legend{display:flex;flex:none;flex-wrap:wrap;gap:7px 12px;border-top:1px solid #aebbc1;background:#edf1f2;padding:9px 11px;color:#485c65;font:500 16px/1.35 system-ui,sans-serif}.classroom-legend span{display:inline-flex;align-items:center;gap:4px}.classroom-legend i{width:12px;height:12px;border:1px solid #73858c;border-radius:50%;background:#aeb7bb}.classroom-legend i[data-status=online]{border-color:#3e7c63;background:#45a27b}.classroom-legend i[data-status=away]{border-color:#a0782d;background:#dcae51}.classroom-legend i[data-status=offline]{border-color:#859399;background:#b6c0c4}.classroom-legend b{color:#65427c;font:600 16px/1 ui-monospace,monospace}.viewer-chrome.is-nvim-fit{display:flex;width:100%;height:100vh;height:100dvh;max-height:100dvh;flex-direction:column;overflow:hidden;background:#11171a}.viewer-chrome.is-nvim-fit .topbar,.viewer-chrome.is-nvim-fit .viewpoint-bar,.viewer-chrome.is-nvim-fit .allocator-panel{flex:none}.viewer-chrome.is-nvim-fit .nvim-panel{display:flex;min-height:0;flex:1;flex-direction:column}.viewer-chrome.is-nvim-fit .nvim-screen-scroll{min-height:0;height:auto!important;flex:1}.viewer-chrome.is-nvim-fit .nvim-resize-handle{display:none}.viewer-chrome.is-nvim-fit .nvim-screen-stage{width:100%;height:100%;margin:0;zoom:1}.viewer-chrome.is-nvim-fit .nvim-screen-content{right:1mm;overflow:hidden;scrollbar-width:none}.viewer-chrome.is-nvim-fit .nvim-screen-grid{display:flex;width:100%;min-height:0;height:100%;align-items:center;justify-content:center}.viewer-chrome.is-nvim-fit .nvim-screen{width:auto!important;height:auto!important;max-width:100%;max-height:100%}.viewer-chrome.is-nvim-fit .nvim-screen-content::-webkit-scrollbar{display:none}.topbar-nvim-section{display:flex;align-items:center;min-width:0;gap:6px}.nvim-toolbar-boundary{flex:none;color:#6f7d83;font:700 14px/1 ui-monospace,monospace}.nvim-toolbar-context{flex:none;width:5ch;color:#263238;font:800 12px/1 ui-monospace,monospace;letter-spacing:.08em;text-align:center}.nvim-toolbar-actions{display:flex;align-items:center;gap:3px}.nvim-toolbar-button{display:inline-flex;align-items:center}.nvim-toolbar-key{flex:0 0 2ch;color:#267697;font:inherit;text-align:center}.nvim-toolbar-label{flex:0 0 5ch;max-width:5ch;overflow:hidden;text-align:left;white-space:nowrap}.topbar-nvim-meta{display:flex;align-items:baseline;min-width:0;max-width:min(44vw,720px);gap:6px}.topbar-nvim-meta strong,.topbar-nvim-meta small{overflow:auto;text-overflow:ellipsis;white-space:nowrap}.topbar-nvim-meta strong{color:#263238;font:700 12px/1.25 system-ui,sans-serif}.topbar-nvim-meta small{color:#66747a;font:500 10px/1.25 system-ui,sans-serif}.viewer-nvim-toggle{border:1px solid #68747a;border-radius:3px;background:#fff;color:#263238;padding:2px 8px;font:650 16px/1.3 system-ui,sans-serif;cursor:pointer}.viewer-nvim-toggle[aria-pressed=true]{border-color:#267697;background:#eaf6fb;color:#174d64}.allocator-panel{width:100%;box-sizing:border-box;border-top:1px solid #40515a;border-bottom:1px solid #40515a;background:#f7f9f5;color:#182127;box-shadow:0 4px 12px #0002;font:11px/1.25 system-ui,sans-serif}.allocator-panel>header{display:grid;grid-template-columns:125px minmax(260px,auto) 1fr;gap:8px;align-items:baseline;min-height:22px;padding:3px 8px;border-bottom:1px solid #b7c1c4;background:#e8eef0}.allocator-panel>header>span{color:#166987;font:800 9px/1.2 ui-monospace,monospace;letter-spacing:.06em}.allocator-panel>header>strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:750 11px/1.2 ui-monospace,monospace}.allocator-panel>header>small{overflow:hidden;color:#536168;text-overflow:ellipsis;white-space:nowrap}.allocator-panel-body{display:grid;grid-template-columns:230px minmax(420px,1fr) minmax(310px,.8fr);gap:9px;align-items:stretch;padding:5px 8px 7px}.allocator-metrics{display:grid;grid-template-columns:1fr 1fr;gap:3px 8px;margin:0}.allocator-metrics>div{min-width:0;border-left:2px solid #8ea2ab;padding-left:5px}.allocator-metrics dt{color:#627077;font:750 7px/1.2 ui-monospace,monospace;letter-spacing:.05em}.allocator-metrics dd{margin:1px 0 0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:700 10px/1.2 ui-monospace,monospace}.allocator-arena-view{min-width:0}.allocator-arena-view.is-inactive{opacity:.42}.allocator-arena-labels{display:flex;justify-content:space-between;gap:8px;margin-bottom:3px;color:#536168;font:700 8px/1.2 ui-monospace,monospace}.allocator-arena-labels strong{color:#075f80}.allocator-pinned-arena{position:relative;display:grid;grid-template-columns:repeat(var(--allocator-size),minmax(2px,1fr));height:32px;border:1px solid #28343a;background:#fff}.allocator-pinned-arena i{min-width:0;border-right:1px solid #e1e5e6;background:#fbfcfc}.allocator-pinned-arena i:nth-child(8n){border-right-color:#87969d}.allocator-pinned-arena i.is-allocated{border-right-color:#b2d8e6;background:#43a8ce}.allocator-cursor{position:absolute;z-index:2;top:-4px;bottom:-4px;width:2px;background:#d02d25;transform:translate(-1px);box-shadow:0 0 0 1px #fff9}.allocator-cursor:before{content:"allocp";position:absolute;top:1px;left:4px;color:#8d1d17;font:800 7px/1 ui-monospace,monospace}.allocator-facts{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:2px 7px;min-width:0}.allocator-facts>div{display:flex;min-width:0;justify-content:space-between;gap:5px;border-bottom:1px dotted #b4bec2}.allocator-facts span{overflow:hidden;color:#46565e;text-overflow:ellipsis;white-space:nowrap}.allocator-facts code{overflow:hidden;color:#142e39;text-overflow:ellipsis;white-space:nowrap;font:700 9px/1.3 ui-monospace,monospace}.allocator-facts p{grid-column:1 / -1;margin:0;color:#647278}.nvim-panel{width:100%;box-sizing:border-box;border-bottom:1px solid #52616a;background:var(--desk, #eee);color:#e7eef1;box-shadow:0 5px 16px #0004;font:12px/1.3 system-ui,sans-serif}.nvim-status{flex:none;border:1px solid #66757c;border-radius:999px;padding:1px 6px;color:#c9d3d8;font:750 9px/1.35 ui-monospace,monospace;letter-spacing:.04em}.nvim-status--connected,.nvim-status--ready{border-color:#31a86d;background:#173b2a;color:#8aefb7}.nvim-status--connecting,.nvim-status--replaying,.nvim-status--verifying{border-color:#c38f2a;background:#3e321b;color:#ffd47d}.nvim-status--failed,.nvim-status--offline,.nvim-status--unselected{border-color:#a34b4b;background:#3b2020;color:#faa}.nvim-control-indicator{flex:none;border:1px solid #60747e;border-radius:3px;background:#222e34;color:#b9c6cc;padding:3px 8px;font:700 10px/1.2 ui-monospace,monospace;letter-spacing:.04em;white-space:nowrap;cursor:pointer}.nvim-control-indicator.is-active{border-color:#3cb3e2;background:#17465a;color:#d9f5ff}.nvim-control-indicator.is-engaged{box-shadow:inset 0 0 0 1px #8ee4ff}.nvim-work-indicator{flex:none;border:1px solid #7a878d;border-radius:3px;background:#f4f6f7;color:#56646a;padding:3px 8px;font:700 10px/1.2 ui-monospace,monospace;letter-spacing:.03em;white-space:nowrap;cursor:pointer}.nvim-work-indicator.is-active{border-color:#cf3d31;background:#fff0ee;color:#9f241b}.nvim-work-indicator.is-engaged{box-shadow:inset 0 0 0 1px #cf3d31}.nvim-work-indicator:disabled{border-color:#a8b0b4;background:#e6e9ea;color:#7c878c;cursor:not-allowed;opacity:.8}.nvim-height-toggle{flex:none;border:1px solid #75848b;border-radius:3px;background:#f4f6f7;color:#425159;padding:3px 8px;font:700 10px/1.2 ui-monospace,monospace;letter-spacing:.03em;white-space:nowrap;cursor:pointer}.nvim-height-toggle.is-active{border-color:#267697;background:#eaf6fb;color:#174d64}.topbar-nvim-section .nvim-toolbar-button{flex:0 0 9.5ch;width:9.5ch;min-width:9.5ch;max-width:9.5ch;height:24px;box-sizing:border-box;justify-content:flex-start;gap:.55ch;padding:2px .55ch;font:750 12px/1 ui-monospace,monospace;letter-spacing:0}.nvim-shortcut-strip{flex:none;color:#536168;font:650 10px/1.2 ui-monospace,monospace;white-space:nowrap}.nvim-shortcut-strip kbd{color:#263238;font:inherit}.nvim-screen-scroll{position:relative;width:100%;box-sizing:border-box;height:var(--nvim-panel-height, 75vh);max-height:none;min-height:240px;overflow:auto;overscroll-behavior:contain;background:var(--desk, #eee);outline:none;scrollbar-color:#58666d #171d20}.nvim-screen-stage{position:relative;width:210mm;margin:0 auto;background:#fff;zoom:var(--viewer-canvas-scale)}.nvim-screen-surface{position:absolute;inset:0 4mm;box-sizing:border-box;padding:1mm;background:#fff}.nvim-screen-frame{position:relative;height:100%;box-sizing:border-box;border:1px solid #267697;background:#101315;box-shadow:0 0 0 3px #000}.nvim-screen-scroll[data-sync=on] .nvim-screen-frame{border-color:#35b9ef}.nvim-screen-scroll[data-control=true] .nvim-screen-frame{box-shadow:0 0 0 3px #cf3d31}.nvim-screen-scroll[data-control=true]{cursor:text}.nvim-screen-content{position:absolute;inset:1mm -5mm 1mm 1mm;overflow-x:hidden;overflow-y:scroll;overscroll-behavior:contain;background:transparent;scrollbar-width:thin;scrollbar-color:#829097 #fff}.nvim-screen-grid{width:calc(100% - 6mm);min-height:100%;background:#101315}.nvim-screen-content::-webkit-scrollbar{width:.8mm}.nvim-screen-content::-webkit-scrollbar-track{background:#fff}.nvim-screen-content::-webkit-scrollbar-thumb{border:.12mm solid #fff;border-radius:99px;background:#829097}.nvim-screen-content::-webkit-scrollbar-thumb:hover{background:#aeb9be}.nvim-screen{display:block;max-width:none;margin:0;outline:none;image-rendering:auto}.nvim-screen:focus{box-shadow:none}.nvim-screen-empty{position:absolute;inset:0;display:grid;place-content:center;gap:5px;padding:16px;color:#9cabb2;text-align:center}.nvim-screen-empty strong{color:#e0e8eb;font:750 11px/1.3 ui-monospace,monospace}.nvim-resize-handle{position:relative;width:100%;height:12px;border-top:1px solid #46565e;background:#182126;cursor:ns-resize;touch-action:none}.nvim-resize-handle:after{content:"";position:absolute;top:4px;left:50%;width:min(160px,22vw);height:3px;border-radius:999px;background:#6c7e87;transform:translate(-50%)}.nvim-resize-handle:hover,.nvim-resize-handle:focus-visible{background:#23323a;outline:none}.nvim-resize-handle:hover:after,.nvim-resize-handle:focus-visible:after{background:#35b9ef}.shortcut-help-backdrop{position:fixed;z-index:200;inset:0;display:grid;place-items:center;padding:20px;background:#10171dcc}.shortcut-help{width:min(680px,calc(100vw - 40px));max-height:calc(100vh - 40px);overflow:auto;border:1px solid #71818a;border-radius:6px;background:#f8fafb;color:#172127;box-shadow:0 18px 60px #0008;font:14px/1.4 system-ui,sans-serif}.shortcut-help>header{display:flex;align-items:start;justify-content:space-between;gap:16px;padding:16px 18px;border-bottom:1px solid #c8d1d5;background:#e8eef1}.shortcut-help header small{color:#52646d;font:750 10px/1.2 ui-monospace,monospace;letter-spacing:.08em}.shortcut-help h2{margin:4px 0 0;font-size:17px}.shortcut-help header button{width:32px;height:32px;border:1px solid #71818a;border-radius:3px;background:#fff;font:22px/1 system-ui,sans-serif;cursor:pointer}.shortcut-help dl{margin:0;padding:10px 18px}.shortcut-help dl>div{display:grid;grid-template-columns:minmax(180px,.8fr) 1.5fr;gap:14px;align-items:center;padding:8px 0;border-bottom:1px solid #dde3e6}.shortcut-help dt,.shortcut-help dd{margin:0}.shortcut-help kbd{display:inline-block;border:1px solid #839198;border-bottom-width:2px;border-radius:3px;background:#fff;padding:3px 7px;font:700 12px/1.2 ui-monospace,monospace}.shortcut-help>p{margin:0;padding:4px 18px 16px;color:#526068}.memory-react{border:1px solid #555;background:#fff;font:14px/1.35 system-ui,sans-serif;padding:10px;color:#1b1b1b}.memory-react>header{display:flex;align-items:center;justify-content:space-between;gap:12px;border-bottom:1px solid #bbb;padding-bottom:7px}.memory-react>header small{display:block;margin-bottom:2px;color:#555;font:700 10px/1.2 ui-monospace,monospace;letter-spacing:.08em}.memory-react h3{font-size:15px;margin:0}.memory-actions{display:flex;gap:5px;flex-wrap:wrap;justify-content:flex-end}.memory-actions button{border:1px solid #777;background:#fff;padding:4px 8px;cursor:pointer}.memory-actions button[aria-pressed=true]{background:#dff3fb;border-color:#237da1;font-weight:700}.memory-lanes{display:grid;grid-template-columns:repeat(auto-fit,minmax(125px,1fr));gap:6px;margin:8px 0}.memory-lane{min-width:0;display:flex;flex-direction:column;align-items:stretch;gap:3px;border:1px solid #999;background:#fafafa;padding:7px;text-align:left}.memory-lane.compile{background:#fff8dc}.memory-lane.bss{background:#edf7fb}.memory-lane.stack{background:#f3f8ef}.memory-lane.registers{background:#f4f4f4}.memory-lane.text{background:#f7f0fa}.memory-lane>b{font-size:11px;letter-spacing:.04em}.memory-lane button{border:1px solid transparent;background:transparent;padding:3px;text-align:left;cursor:pointer}.memory-lane button:hover,.memory-lane button.active{border-color:#238bb7;background:#fff}.memory-lane code{font-size:10px;white-space:normal}.arena-wrap{margin:12px 0 6px}.arena-label{display:flex;justify-content:space-between;margin-bottom:4px}.arena{--allocp: 0;--arena-size: 64;position:relative;display:grid;grid-template-columns:repeat(var(--arena-size),1fr);height:40px;border:1px solid #222;margin-bottom:28px}.arena i{border-right:1px solid #ddd}.arena .allocp{position:absolute;left:calc(var(--allocp) / var(--arena-size) * 100%);top:43px;transform:translate(-50%);color:#087da8;font:700 11px/1.2 ui-monospace,monospace;white-space:nowrap}.memory-stage{border-top:1px solid #ddd;padding-top:7px;margin:0}.memory-inspector{display:grid;grid-template-columns:auto 1fr;gap:2px 10px;margin-top:7px;padding:7px;border-left:3px solid #238bb7;background:#f5fbfd}.memory-inspector strong{grid-row:1/3}.memory-inspector code{font-size:11px}.memory-inspector span{font-size:12px}.asset-static-fallback{display:none}.uml-react{border:1px solid #555;background:#fff;font:13px/1.35 system-ui,sans-serif;color:#1b1b1b}.uml-react>header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:6px 8px;border-bottom:1px solid #bbb;background:#fafafa}.uml-react>header small{display:block;color:#555;font:700 10px/1.2 ui-monospace,monospace;letter-spacing:.06em}.uml-react h3{margin:2px 0 0;font-size:15px}.uml-actions{display:flex;gap:3px;flex-wrap:wrap;justify-content:flex-end}.uml-actions button{border:1px solid #aaa;border-radius:2px;background:#fcfcfc;color:#444;padding:2px 6px;font:600 10px/1.25 ui-monospace,monospace;cursor:pointer}.uml-actions button:hover{border-color:#64879a;background:#f5fafc}.uml-actions button[data-completed=true]{border-color:#62a47f;background:#edf8f1;color:#14613a}.uml-actions button[aria-pressed=true]{border-color:#287495;background:#edf7fb;color:#174d64;box-shadow:inset 0 0 0 1px #b9dce9}.uml-canvas{display:block;overflow-x:hidden;overflow-y:visible;box-sizing:border-box;padding:8px 10px;text-align:center}.uml-svg-host{width:max-content;min-width:100%}.uml-canvas img,.uml-svg-host svg{display:block;width:auto!important;max-width:none!important;height:auto!important;max-height:none;margin-inline:auto;object-fit:contain}.uml-svg-host{cursor:pointer}.uml-svg-host.is-tiled{position:relative;width:var(--uml-tile-page-width);min-width:var(--uml-tile-page-width);height:var(--uml-tile-page-height);overflow:hidden;cursor:default}.uml-svg-host.is-tiled>svg{position:absolute;top:var(--uml-tile-top);left:var(--uml-tile-left);margin:0}.uml-static-crop{position:absolute;top:var(--uml-tile-top);left:var(--uml-tile-left);overflow:hidden}.uml-static-crop>img{display:block;max-width:none!important;max-height:none!important;margin:0!important;object-fit:fill!important;transform-origin:0 0}.uml-tile-part{display:inline-block;margin-left:.65em;color:#607985;font:650 9px/1.2 ui-monospace,monospace;letter-spacing:.03em;white-space:nowrap}.uml-phase-actions>span{display:inline-flex;align-items:center;border:1px solid #9eb2bd;border-radius:2px;background:#f1f5f7;color:#3d5967;padding:2px 6px;font:700 10px/1.25 ui-monospace,monospace}.uml-stage-region{cursor:pointer;transition:fill .14s ease,stroke .14s ease,stroke-width .14s ease}.uml-stage-region-completed.uml-stage-region-bg{fill:#e7f6ed!important;fill-opacity:1!important;stroke:#258154!important;stroke-width:3px!important}.uml-stage-region-completed.uml-stage-region-outline{stroke:#258154!important;stroke-width:3px!important}.uml-stage-region-active.uml-stage-region-bg{fill:#e7f6fc!important;fill-opacity:1!important;stroke:#187ea7!important;stroke-width:3px!important}.uml-stage-region-active.uml-stage-region-outline{stroke:#187ea7!important;stroke-width:3px!important}.uml-stage-region-active.uml-stage-region-completed.uml-stage-region-bg{fill:#e7f6ed!important}.uml-stage-region:focus{outline:none;stroke:#005f87!important;stroke-width:4px!important}.uml-inline-warning{display:block;padding-top:5px;color:#8b2d20;font-size:11px}.uml-stage{display:grid;grid-template-columns:auto 1fr;gap:3px 10px;margin:0 8px 8px;padding:8px;border-left:3px solid #315f78;background:#eef7fb}.uml-stage strong{grid-row:1/3}.uml-stage code{grid-column:2;font-size:11px}.uml-approval{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0 8px 8px;padding-top:6px;border-top:1px solid #d2d2d2;color:#555;font-size:11px}.uml-approval button{flex:0 0 auto;border:1px solid #477a5d;border-radius:2px;background:#edf8f1;color:#174e30;padding:3px 7px;font:600 10px/1.25 system-ui,sans-serif;cursor:pointer}.uml-approval button:disabled{border-color:#bbb;background:#f3f3f3;color:#888;cursor:not-allowed}.uml-react>.uml-snapshot-header{display:block;padding:5px 8px 4px}.uml-header-first-line{display:flex;align-items:center;justify-content:space-between;gap:10px}.uml-header-first-line h3{flex:1 1 auto;min-width:0;margin:0;color:#20282c;text-align:left;font-size:12px;line-height:1.25}.uml-header-first-line h3>span{color:#245f7a;font-family:ui-monospace,monospace;letter-spacing:.04em}.uml-header-description{max-width:620px;margin:2px 0 0;color:#555;text-align:left;font-size:8px}.uml-after-description{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:baseline;gap:4mm;margin-top:3mm;border:1px solid #b8c2c7;border-left:1.2mm solid #2b7898;background:#f7fafb;padding:2.2mm 3mm;color:#26363e;text-align:left}.uml-after-description strong{color:#245f7a;font:800 8px/1.25 ui-monospace,monospace;letter-spacing:.06em;text-transform:uppercase}.uml-after-description p{margin:0;font:400 10px/1.35 system-ui,sans-serif}.uml-header-navigation{display:flex;flex:0 0 auto;align-items:center;justify-content:flex-end;gap:6px}.uml-page-continuation{display:inline-flex;flex:0 0 auto;align-items:center;gap:2px;min-height:22px;border:1px solid #86a5b3;border-radius:3px;background:#edf7fa;padding:1px 5px;color:#126b8c;font:850 15px/1 ui-monospace,monospace;animation:uml-continuation-pulse 1.15s ease-in-out infinite alternate}.uml-page-continuation i{display:inline-grid;min-width:14px;place-items:center;font-style:normal}@keyframes uml-continuation-pulse{0%{border-color:#9fb5be;background:#f6fafb;color:#4c7180;box-shadow:none}to{border-color:#167ca4;background:#dff2f8;color:#075c7c;box-shadow:0 0 0 2px #8dcde433}}@media(prefers-reduced-motion:reduce){.uml-page-continuation{animation:none}}.uml-phase-actions,.uml-step-actions{display:flex;align-items:center;flex-wrap:wrap;gap:3px}.uml-phase-actions{flex:0 0 auto;flex-wrap:nowrap;justify-content:flex-end;white-space:nowrap}.uml-phase-actions button,.uml-step-actions button,.uml-edit-toggle,.uml-snapshot-cursor button{border:1px solid #aaa;border-radius:2px;background:#fff;color:#444;cursor:pointer;font:600 9px/1.2 ui-monospace,monospace}.uml-edit-toggle{min-width:42px;padding:3px 7px;border:1px solid #8a9ca5;border-radius:2px;background:#f7f9fa;color:#334d59;cursor:pointer;font:750 9px/1.2 ui-monospace,monospace;letter-spacing:.04em}.uml-edit-toggle.is-active{border-color:#b56b16;background:#fff3df;color:#713c00;box-shadow:inset 0 0 0 1px #f0c78f}.uml-phase-actions button{padding:3px 7px}.uml-phase-actions button[aria-pressed=true]{border-color:#287495;background:#edf7fb;color:#174d64}.uml-step-actions{min-height:0;padding:0;border:0;background:transparent}.uml-step-actions>span{margin-right:4px;color:#555;font:700 9px/1.2 ui-monospace,monospace}.uml-step-actions button{min-width:25px;padding:2px 4px}.uml-step-actions button[data-completed=true]{border-color:#62a47f;background:#edf8f1;color:#14613a}.uml-step-actions button[aria-pressed=true]{border-color:#287495;background:#287495;color:#fff}.uml-step-actions button[data-mode=CODE]:after,.uml-step-actions button[data-mode=RUN]:after{margin-left:3px;font-size:7px;letter-spacing:.02em}.uml-step-actions button[data-mode=CODE]:after{content:"C"}.uml-step-actions button[data-mode=RUN]:after{content:"R"}.uml-snapshot-layout{background:#fff;border-bottom:1px solid #ddd}.uml-step-canvas{min-width:0}.uml-step-canvas .uml-svg-host svg{max-height:none}.uml-step-hit{cursor:pointer;fill:transparent}.uml-step-hit-active{fill:#3ea6ce!important;fill-opacity:.08!important}.uml-step-element{transition:fill .12s ease,stroke .12s ease,stroke-width .12s ease}line.uml-step-element-completed:not(.uml-step-element-active){stroke:#258154!important;stroke-width:1.8px!important}polygon.uml-step-element-completed:not(.uml-step-element-active){fill:#258154!important;stroke:#258154!important}text.uml-step-element-completed:not(.uml-step-element-active){fill:#1d7049!important}line.uml-step-element-active{stroke:#087ca8!important;stroke-width:2.6px!important}polygon.uml-step-element-active{fill:#087ca8!important;stroke:#087ca8!important}text.uml-step-element-active{fill:#075d7d!important;font-weight:700!important}.uml-step-hit:focus{outline:none;stroke:#bd554d;stroke-width:.8px;stroke-dasharray:3 3}.uml-class-svg-active>g>*:not(.uml-class-focus-region):not(.uml-class-step-hit):not(.uml-layout-hit):not(.uml-layout-resize){opacity:.62;transition:opacity .12s ease}.uml-class-svg-active .uml-step-element:not(.uml-step-element-active){opacity:.98!important}.uml-class-svg-active text.uml-step-element-tone-a:not(.uml-step-element-active){fill:#174f69!important}.uml-class-svg-active text.uml-step-element-tone-b:not(.uml-step-element-active){fill:#586581!important}.uml-class-svg-active rect.uml-step-element-tone-a:not(.uml-step-element-active){fill:#eaf5f8!important;stroke:#3f7890!important}.uml-class-svg-active rect.uml-step-element-tone-b:not(.uml-step-element-active){fill:#f2eef8!important;stroke:#7b86a1!important}.uml-class-svg-active path.uml-step-element-tone-a:not(.uml-step-element-active),.uml-class-svg-active line.uml-step-element-tone-a:not(.uml-step-element-active),.uml-class-svg-active polygon.uml-step-element-tone-a:not(.uml-step-element-active){stroke:#3f7890!important}.uml-class-svg-active path.uml-step-element-tone-b:not(.uml-step-element-active),.uml-class-svg-active line.uml-step-element-tone-b:not(.uml-step-element-active),.uml-class-svg-active polygon.uml-step-element-tone-b:not(.uml-step-element-active){stroke:#7b86a1!important}.uml-class-svg-active .uml-step-element-active{opacity:1!important}.uml-class-svg-active text.uml-step-element-active{fill:#b42318!important;font-weight:700!important}.uml-class-svg-active path.uml-step-element-active,.uml-class-svg-active line.uml-step-element-active,.uml-class-svg-active polygon.uml-step-element-active,.uml-class-svg-active rect.uml-step-element-active{stroke:#bd554d!important;stroke-width:1.35px!important}.uml-class-svg-active polygon.uml-step-element-active{fill:#bd554d!important}.uml-class-focus-region{display:none}.uml-step-canvas.is-layout-editing{position:relative;touch-action:none;background-color:#fbfcfd;background-image:linear-gradient(#7d98a20d 1px,transparent 1px),linear-gradient(90deg,#7d98a20d 1px,transparent 1px);background-size:12px 12px}.uml-layout-editing .uml-step-hit,.uml-layout-editing .uml-class-focus-region{pointer-events:none}.uml-layout-hit{fill:#4e9ec4;fill-opacity:.035;stroke:#247a9e;stroke-width:1.1px;stroke-dasharray:5 3;vector-effect:non-scaling-stroke;cursor:grab;pointer-events:all}.uml-layout-hit:hover{fill-opacity:.09;stroke-width:1.6px}.uml-layout-hit.is-selected{fill:#f7a928;fill-opacity:.08;stroke:#b66b00;stroke-width:1.8px;stroke-dasharray:none}.uml-layout-hit:active{cursor:grabbing}.uml-layout-resize{fill:#fff;stroke:#b66b00;stroke-width:1.5px;vector-effect:non-scaling-stroke;cursor:nwse-resize;pointer-events:all}.uml-layout-editor{display:grid;grid-template-columns:minmax(140px,.8fr) minmax(240px,1.8fr) minmax(260px,1.4fr);gap:7px 10px;align-items:end;padding:8px 10px 9px;border-top:2px solid #b97822;background:#f7f5f0;color:#26343a;text-align:left;font:10px/1.3 system-ui,sans-serif}.uml-layout-editor-title{grid-column:1/-1;display:flex;align-items:baseline;gap:9px}.uml-layout-editor-title strong{color:#713c00;font:800 11px/1.2 ui-monospace,monospace;text-transform:uppercase}.uml-layout-editor-title span{color:#65737a}.uml-layout-editor-title b{margin-left:auto;color:#9c2f1b;font-size:9px}.uml-layout-editor label{display:grid;gap:2px;color:#52636b;font:700 8px/1.2 ui-monospace,monospace;text-transform:uppercase}.uml-layout-editor input,.uml-layout-editor select,.uml-layout-editor textarea{box-sizing:border-box;width:100%;min-width:0;border:1px solid #9ca9ae;border-radius:2px;background:#fff;color:#172126;padding:4px 5px;font:10px/1.25 ui-monospace,monospace;text-transform:none}.uml-layout-editor textarea{resize:vertical;line-height:1.35}.uml-layout-number-grid,.uml-layout-style-grid{display:grid;gap:5px}.uml-layout-number-grid{grid-template-columns:repeat(4,minmax(48px,1fr))}.uml-layout-style-grid{grid-template-columns:repeat(5,minmax(54px,1fr))}.uml-layout-editor-text{align-self:stretch}.uml-layout-editor input[type=color]{min-height:27px;padding:2px}.uml-layout-editor-actions{grid-column:1/-1;display:flex;gap:5px}.uml-layout-editor-actions button{border:1px solid #87979f;border-radius:2px;background:#fff;color:#283940;padding:4px 8px;cursor:pointer;font:700 9px/1.2 ui-monospace,monospace}.uml-layout-editor-actions button.is-primary{border-color:#287495;background:#287495;color:#fff}.uml-layout-editor-actions button.is-danger{margin-left:auto;border-color:#a26156;color:#812b1c}.uml-layout-editor-actions button:disabled{opacity:.45;cursor:not-allowed}.uml-layout-editor output{grid-column:1/-1;min-height:1.2em;color:#52636b;font:9px/1.25 ui-monospace,monospace}@media(max-width:900px){.uml-layout-editor{grid-template-columns:1fr}.uml-layout-editor-title,.uml-layout-editor-actions,.uml-layout-editor output{grid-column:1}}@media print{.uml-edit-toggle,.uml-layout-editor,.uml-layout-hit,.uml-layout-resize{display:none!important}}.uml-snapshot-panel{display:grid;gap:8px;min-width:0;padding:8px;background:#fafafa;font-size:10px}.uml-snapshot-panel>header{display:flex;justify-content:space-between;gap:8px;align-items:center}.uml-snapshot-panel>header>div:first-child>span,.uml-snapshot-panel>header>div:first-child>strong{display:block}.uml-snapshot-panel>header>div:first-child>span{color:#5a5a5a;font:700 9px/1.2 ui-monospace,monospace}.uml-snapshot-panel>header>div:first-child>strong{font-size:12px}.uml-snapshot-panel>header>div:first-child>strong code{display:inline-block;min-width:24px;color:#075d7d}.uml-snapshot-cursor{display:flex;align-items:center;gap:4px}.uml-snapshot-cursor button{width:23px;height:23px;padding:0}.uml-snapshot-cursor button:disabled{opacity:.35;cursor:default}.uml-snapshot-cursor output{min-width:34px;text-align:center;font:700 9px/1 ui-monospace,monospace}.uml-snapshot-description{margin:0;color:#333}.uml-snapshot-panel section{min-width:0;border:1px solid #d0d0d0;background:#fff;padding:7px}.uml-snapshot-panel h4{margin:0 0 4px;color:#315f78;font:700 9px/1.2 ui-monospace,monospace;text-transform:uppercase}.uml-state-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:7px;min-width:0}.uml-snapshot-registers dl{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:3px;margin:0}.uml-snapshot-registers dl>div{display:grid;grid-template-columns:auto 1fr;gap:5px;min-width:0;padding:2px 4px;border-left:2px solid #bbb;background:#f6f6f6}.uml-snapshot-registers dl>div.primary{grid-column:1/-1;border-color:#287495;background:#edf7fb}.uml-snapshot-registers dt{color:#555;font-weight:700}.uml-snapshot-registers dd,.uml-snapshot-variables dd,.uml-snapshot-memory dd{min-width:0;margin:0;overflow-wrap:anywhere;text-align:right;font:600 9px/1.25 ui-monospace,monospace}.uml-snapshot-variables dl{display:grid;gap:3px;margin:0}.uml-snapshot-variables dl>div{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:5px;padding:3px 5px;border-left:2px solid #5f899b;background:#f5f8f9}.uml-snapshot-variables dt{font-weight:700}.uml-snapshot-variables dt small{display:block;color:#777;font:8px/1.2 ui-monospace,monospace}.uml-snapshot-variables [data-state=uninitialised],.uml-stack-lane article[data-state=uninitialised]{color:#777;background:#f5f5f5}.uml-snapshot-variables [data-state=uninitialised] dt:after,.uml-stack-lane article[data-state=uninitialised]>strong:after{content:" ?";color:#9a6a00}.uml-arena-snapshot>div{position:relative;height:10px;border:1px solid #666;background:repeating-linear-gradient(90deg,#fff 0 7px,#eee 7px 8px)}.uml-arena-snapshot>div span{display:block;height:100%;background:#b8ddeb}.uml-arena-snapshot>div i{position:absolute;top:-3px;bottom:-3px;width:2px;background:#075d7d;transform:translate(-1px)}.uml-arena-snapshot>p{display:grid;grid-template-columns:1fr auto 1fr;gap:4px;margin:3px 0;font-size:8px}.uml-arena-snapshot>p code:last-child{text-align:right}.uml-arena-bytes{display:block;margin-bottom:4px;overflow-wrap:anywhere;color:#666;font-size:8px}.uml-snapshot-memory dl{display:grid;gap:2px;margin:0}.uml-snapshot-memory dl>div{display:grid;grid-template-columns:1fr auto;gap:4px;padding-top:2px;border-top:1px solid #eee}.uml-snapshot-memory dt{font-weight:700}.uml-snapshot-memory dt small{display:block;color:#777;font:8px/1.2 ui-monospace,monospace}.uml-snapshot-stack>header{display:flex;align-items:baseline;justify-content:space-between;gap:8px}.uml-snapshot-stack>header>p{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:4px 9px;margin:0;color:#555;font-size:8px}.uml-stack-axis{display:flex;justify-content:space-between;gap:8px;margin:3px 89px 2px;color:#777;font:8px/1.2 ui-monospace,monospace}.uml-stack-lane{display:flex;align-items:stretch;min-width:0;overflow-x:auto;border:1px solid #66757c;background:#eef1f2}.uml-stack-boundary{display:grid;place-content:center;flex:0 1 82px;min-width:64px;padding:5px;background:#273941;color:#fff;text-align:center;font:700 8px/1.25 ui-monospace,monospace}.uml-stack-boundary small{color:#a9d9eb;font-size:8px;text-transform:uppercase}.uml-stack-gap{display:grid;place-content:center;flex:.7 1 70px;min-width:60px;min-height:52px;border-right:1px dashed #9da8ad;color:#78858b;text-align:center;font:8px/1.2 ui-monospace,monospace}.uml-stack-gap strong,.uml-stack-gap small{display:block}.uml-stack-gap strong{color:#52636b}.uml-stack-gap small{color:#78858b;font-size:8px}.uml-stack-lane article{display:grid;align-content:center;flex:1 1 92px;min-width:76px;padding:5px 7px;border-right:1px solid #9da8ad;background:#fff;text-align:center}.uml-stack-lane article[data-state=initialised]{border-top:3px solid #2c8060}.uml-stack-lane article[data-state=saved]{border-top:3px solid #6d7196;background:#f7f6fb}.uml-stack-lane article strong,.uml-stack-lane article code,.uml-stack-lane article small{display:block;overflow-wrap:anywhere}.uml-stack-lane article strong{font:700 9px/1.2 ui-monospace,monospace}.uml-stack-lane article code{margin:2px 0;color:#1f4555;font-size:8px}.uml-stack-lane article small{color:#777;font:8px/1.2 ui-monospace,monospace}.uml-snapshot-observation p{margin:0 0 3px}.uml-snapshot-observation small{display:block;overflow-wrap:anywhere;color:#666;font:8px/1.3 ui-monospace,monospace}.task-conclusion{margin:8px 0 0;padding:7px 9px;border:1px solid #315f78;background:#f3f9fc}.task-conclusion>strong{display:block;margin-bottom:3px;color:#315f78;font:700 10px/1.2 ui-monospace,monospace}.task-conclusion p{margin:0}.section-sheet--asset .section-heading{align-items:baseline;margin:0 0 1.5mm}.section-sheet--asset .section-heading .section-index{margin-right:4mm;color:#68757b;font-size:10px;font-weight:600}.section-sheet--asset .section-heading .section-title{color:#20282c;font:750 13px/1.2 system-ui,sans-serif}.section-sheet--asset .card-section>p{max-width:110ch;margin:0 0 2mm;color:#58656b;font:9px/1.3 system-ui,sans-serif}.section-sheet--asset .card-asset{margin-top:2mm}.lesson-progress-control{display:inline-flex;align-items:center;margin-right:7px;padding:2px 7px;border:1px solid #8c8c8c;border-radius:12px;background:#fff;cursor:pointer}.lesson-progress-control:disabled{cursor:wait;opacity:.6}.lesson-progress-control[data-status=approved]{background:#e7f6ed;border-color:#18794e}.lesson-progress-topbar{display:flex;flex:none;align-items:center;gap:5px;min-width:0;padding-left:7px;border-left:1px solid #a9b3b8;color:#33444c;font:650 10px/1.2 system-ui,sans-serif;white-space:nowrap}.lesson-progress-topbar strong{color:#52636b;font:800 9px/1 ui-monospace,monospace;letter-spacing:.05em}.lesson-progress-topbar output{min-width:3.8ch;border:1px solid #91a1a8;border-radius:999px;background:#fff;padding:2px 6px;color:#1e566e;text-align:center;font:800 9px/1 ui-monospace,monospace}.lesson-progress-topbar a{color:#245f7a;text-decoration:none}.lesson-progress-topbar a:hover,.lesson-progress-topbar a:focus-visible{text-decoration:underline}.lesson-progress-error{color:#9b1c1c;font-weight:900}.step-row{display:flex;align-items:center;margin:4px 0}.section-anchor{position:relative;top:-45px}.app-load-error{max-width:760px;margin:10vh auto;padding:24px;border:1px solid #9b1c1c;background:#fff;font-family:system-ui,sans-serif}.app-load-error pre{white-space:pre-wrap}@media(max-width:800px){.viewpoint-bar{grid-template-columns:repeat(4,minmax(0,1fr))}.viewpoint-bar button{min-height:28px}.card-library-toggle>span:last-child{display:none}.card-library-toggle{gap:4px;padding-inline:6px}.topbar-nvim-meta{display:none}.topbar-nvim-section .nvim-control-indicator{max-width:42vw;overflow:hidden;text-overflow:ellipsis}.nvim-shortcut-strip,.nvim-work-indicator{display:none}.allocator-panel>header{grid-template-columns:auto 1fr}.allocator-panel>header>small{display:none}.allocator-panel-body{grid-template-columns:1fr}.allocator-metrics,.allocator-facts{display:none}.memory-lanes{grid-template-columns:1fr 1fr}.memory-react>header,.uml-react>header{align-items:flex-start;flex-direction:column}.uml-header-first-line,.uml-header-navigation{width:100%;align-items:flex-start;flex-direction:column}.uml-header-navigation{gap:4px}.memory-actions{justify-content:flex-start}.uml-snapshot-layout{display:block}.uml-step-canvas{border:0}.uml-phase-actions{width:100%;justify-content:flex-end}.uml-state-grid{grid-template-columns:1fr}.uml-snapshot-stack>header{align-items:flex-start;flex-direction:column}.uml-snapshot-stack>header>p{justify-content:flex-start}.uml-stack-axis{margin-right:0;margin-left:0}}@media print{@page card-portrait{size:A4 portrait;margin:0}@page card-landscape{size:A4 landscape;margin:0}.sheet--portrait{page:card-portrait}.sheet--landscape{page:card-landscape}.paper{display:block;gap:0}.paper>.sheet,.paper>.sheet>.sheet-content,.paper>.sheet-spread>.sheet,.paper>.sheet-spread>.sheet>.sheet-content{height:auto;min-height:0;max-height:none;overflow:visible}.sheet-spread{display:contents!important}.viewer-chrome,.side-panel-dock,.memory-react{display:none!important}.asset-static-fallback{display:block!important;width:100%}.uml-actions,.uml-phase-actions,.uml-step-actions,.uml-snapshot-panel,.uml-stage,.uml-approval,.lesson-progress-topbar,.lesson-progress-control,.uml-page-continuation{display:none!important}.uml-after-description{break-inside:avoid}} diff --git a/web/app.js b/web/app.js new file mode 100644 index 0000000..77bf0cb --- /dev/null +++ b/web/app.js @@ -0,0 +1,61 @@ +var Op=Object.create;var Ss=Object.defineProperty;var $p=Object.getOwnPropertyDescriptor;var Fp=Object.getOwnPropertyNames;var Dp=Object.getPrototypeOf,jp=Object.prototype.hasOwnProperty;var jt=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Up=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Fp(t))!jp.call(e,o)&&o!==n&&Ss(e,o,{get:()=>t[o],enumerable:!(r=$p(t,o))||r.enumerable});return e};var wo=(e,t,n)=>(n=e!=null?Op(Dp(e)):{},Up(t||!e||!e.__esModule?Ss(n,"default",{value:e,enumerable:!0}):n,e));var Is=jt(Y=>{"use strict";var xr=Symbol.for("react.element"),Vp=Symbol.for("react.portal"),Bp=Symbol.for("react.fragment"),Hp=Symbol.for("react.strict_mode"),Kp=Symbol.for("react.profiler"),Wp=Symbol.for("react.provider"),Gp=Symbol.for("react.context"),Yp=Symbol.for("react.forward_ref"),Qp=Symbol.for("react.suspense"),qp=Symbol.for("react.memo"),Xp=Symbol.for("react.lazy"),Ns=Symbol.iterator;function Zp(e){return e===null||typeof e!="object"?null:(e=Ns&&e[Ns]||e["@@iterator"],typeof e=="function"?e:null)}var Cs={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Ls=Object.assign,zs={};function Un(e,t,n){this.props=e,this.context=t,this.refs=zs,this.updater=n||Cs}Un.prototype.isReactComponent={};Un.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};Un.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function As(){}As.prototype=Un.prototype;function Xi(e,t,n){this.props=e,this.context=t,this.refs=zs,this.updater=n||Cs}var Zi=Xi.prototype=new As;Zi.constructor=Xi;Ls(Zi,Un.prototype);Zi.isPureReactComponent=!0;var Es=Array.isArray,Ps=Object.prototype.hasOwnProperty,Ji={current:null},Ts={key:!0,ref:!0,__self:!0,__source:!0};function Rs(e,t,n){var r,o={},i=null,l=null;if(t!=null)for(r in t.ref!==void 0&&(l=t.ref),t.key!==void 0&&(i=""+t.key),t)Ps.call(t,r)&&!Ts.hasOwnProperty(r)&&(o[r]=t[r]);var s=arguments.length-2;if(s===1)o.children=n;else if(1{"use strict";Os.exports=Is()});var Ws=jt(ie=>{"use strict";function ol(e,t){var n=e.length;e.push(t);e:for(;0>>1,o=e[r];if(0<_o(o,t))e[r]=t,e[n]=o,n=r;else break e}}function Nt(e){return e.length===0?null:e[0]}function Lo(e){if(e.length===0)return null;var t=e[0],n=e.pop();if(n!==t){e[0]=n;e:for(var r=0,o=e.length,i=o>>>1;r_o(s,n))u_o(d,s)?(e[r]=d,e[u]=n,r=u):(e[r]=s,e[l]=n,r=l);else if(u_o(d,n))e[r]=d,e[u]=n,r=u;else break e}}return t}function _o(e,t){var n=e.sortIndex-t.sortIndex;return n!==0?n:e.id-t.id}typeof performance=="object"&&typeof performance.now=="function"?($s=performance,ie.unstable_now=function(){return $s.now()}):(tl=Date,Fs=tl.now(),ie.unstable_now=function(){return tl.now()-Fs});var $s,tl,Fs,Tt=[],en=[],rm=1,pt=null,De=3,zo=!1,Sn=!1,kr=!1,Us=typeof setTimeout=="function"?setTimeout:null,Vs=typeof clearTimeout=="function"?clearTimeout:null,Ds=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function il(e){for(var t=Nt(en);t!==null;){if(t.callback===null)Lo(en);else if(t.startTime<=e)Lo(en),t.sortIndex=t.expirationTime,ol(Tt,t);else break;t=Nt(en)}}function ll(e){if(kr=!1,il(e),!Sn)if(Nt(Tt)!==null)Sn=!0,sl(al);else{var t=Nt(en);t!==null&&ul(ll,t.startTime-e)}}function al(e,t){Sn=!1,kr&&(kr=!1,Vs(Sr),Sr=-1),zo=!0;var n=De;try{for(il(t),pt=Nt(Tt);pt!==null&&(!(pt.expirationTime>t)||e&&!Ks());){var r=pt.callback;if(typeof r=="function"){pt.callback=null,De=pt.priorityLevel;var o=r(pt.expirationTime<=t);t=ie.unstable_now(),typeof o=="function"?pt.callback=o:pt===Nt(Tt)&&Lo(Tt),il(t)}else Lo(Tt);pt=Nt(Tt)}if(pt!==null)var i=!0;else{var l=Nt(en);l!==null&&ul(ll,l.startTime-t),i=!1}return i}finally{pt=null,De=n,zo=!1}}var Ao=!1,Co=null,Sr=-1,Bs=5,Hs=-1;function Ks(){return!(ie.unstable_now()-Hse||125r?(e.sortIndex=n,ol(en,e),Nt(Tt)===null&&e===Nt(en)&&(kr?(Vs(Sr),Sr=-1):kr=!0,ul(ll,n-r))):(e.sortIndex=o,ol(Tt,e),Sn||zo||(Sn=!0,sl(al))),e};ie.unstable_shouldYield=Ks;ie.unstable_wrapCallback=function(e){var t=De;return function(){var n=De;De=t;try{return e.apply(this,arguments)}finally{De=n}}}});var Ys=jt((Sh,Gs)=>{"use strict";Gs.exports=Ws()});var Zd=jt(lt=>{"use strict";var om=Eo(),ot=Ys();function L(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Tl=Object.prototype.hasOwnProperty,im=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,Qs={},qs={};function lm(e){return Tl.call(qs,e)?!0:Tl.call(Qs,e)?!1:im.test(e)?qs[e]=!0:(Qs[e]=!0,!1)}function am(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function sm(e,t,n,r){if(t===null||typeof t>"u"||am(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function Qe(e,t,n,r,o,i,l){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=o,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=i,this.removeEmptyString=l}var Fe={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){Fe[e]=new Qe(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];Fe[t]=new Qe(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){Fe[e]=new Qe(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){Fe[e]=new Qe(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){Fe[e]=new Qe(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){Fe[e]=new Qe(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){Fe[e]=new Qe(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){Fe[e]=new Qe(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){Fe[e]=new Qe(e,5,!1,e.toLowerCase(),null,!1,!1)});var Na=/[\-:]([a-z])/g;function Ea(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(Na,Ea);Fe[t]=new Qe(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(Na,Ea);Fe[t]=new Qe(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(Na,Ea);Fe[t]=new Qe(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){Fe[e]=new Qe(e,1,!1,e.toLowerCase(),null,!1,!1)});Fe.xlinkHref=new Qe("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){Fe[e]=new Qe(e,1,!1,e.toLowerCase(),null,!0,!0)});function _a(e,t,n,r){var o=Fe.hasOwnProperty(t)?Fe[t]:null;(o!==null?o.type!==0:r||!(2s||o[l]!==i[s]){var u=` +`+o[l].replace(" at new "," at ");return e.displayName&&u.includes("")&&(u=u.replace("",e.displayName)),u}while(1<=l&&0<=s);break}}}finally{dl=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?Tr(e):""}function um(e){switch(e.tag){case 5:return Tr(e.type);case 16:return Tr("Lazy");case 13:return Tr("Suspense");case 19:return Tr("SuspenseList");case 0:case 2:case 15:return e=pl(e.type,!1),e;case 11:return e=pl(e.type.render,!1),e;case 1:return e=pl(e.type,!0),e;default:return""}}function Ol(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Kn:return"Fragment";case Hn:return"Portal";case Rl:return"Profiler";case Ca:return"StrictMode";case Ml:return"Suspense";case Il:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case rc:return(e.displayName||"Context")+".Consumer";case nc:return(e._context.displayName||"Context")+".Provider";case La:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case za:return t=e.displayName||null,t!==null?t:Ol(e.type)||"Memo";case nn:t=e._payload,e=e._init;try{return Ol(e(t))}catch{}}return null}function cm(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Ol(t);case 8:return t===Ca?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function yn(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function ic(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function dm(e){var t=ic(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var o=n.get,i=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return o.call(this)},set:function(l){r=""+l,i.call(this,l)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(l){r=""+l},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function To(e){e._valueTracker||(e._valueTracker=dm(e))}function lc(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=ic(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function li(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function $l(e,t){var n=t.checked;return xe({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Zs(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=yn(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function ac(e,t){t=t.checked,t!=null&&_a(e,"checked",t,!1)}function Fl(e,t){ac(e,t);var n=yn(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?Dl(e,t.type,n):t.hasOwnProperty("defaultValue")&&Dl(e,t.type,yn(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Js(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function Dl(e,t,n){(t!=="number"||li(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var Rr=Array.isArray;function nr(e,t,n,r){if(e=e.options,t){t={};for(var o=0;o"+t.valueOf().toString()+"",t=Ro.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Wr(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Or={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},pm=["Webkit","ms","Moz","O"];Object.keys(Or).forEach(function(e){pm.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Or[t]=Or[e]})});function dc(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Or.hasOwnProperty(e)&&Or[e]?(""+t).trim():t+"px"}function pc(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,o=dc(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,o):e[n]=o}}var mm=xe({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Vl(e,t){if(t){if(mm[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(L(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(L(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(L(61))}if(t.style!=null&&typeof t.style!="object")throw Error(L(62))}}function Bl(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Hl=null;function Aa(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Kl=null,rr=null,or=null;function nu(e){if(e=co(e)){if(typeof Kl!="function")throw Error(L(280));var t=e.stateNode;t&&(t=Mi(t),Kl(e.stateNode,e.type,t))}}function mc(e){rr?or?or.push(e):or=[e]:rr=e}function fc(){if(rr){var e=rr,t=or;if(or=rr=null,nu(e),t)for(e=0;e>>=0,e===0?32:31-(Nm(e)/Em|0)|0}var Mo=64,Io=4194304;function Mr(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function ci(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,o=e.suspendedLanes,i=e.pingedLanes,l=n&268435455;if(l!==0){var s=l&~o;s!==0?r=Mr(s):(i&=l,i!==0&&(r=Mr(i)))}else l=n&~o,l!==0?r=Mr(l):i!==0&&(r=Mr(i));if(r===0)return 0;if(t!==0&&t!==r&&(t&o)===0&&(o=r&-r,i=t&-t,o>=i||o===16&&(i&4194240)!==0))return t;if((r&4)!==0&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function so(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-zt(t),e[t]=n}function zm(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=Fr),du=" ",pu=!1;function Mc(e,t){switch(e){case"keyup":return rf.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Ic(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Wn=!1;function lf(e,t){switch(e){case"compositionend":return Ic(t);case"keypress":return t.which!==32?null:(pu=!0,du);case"textInput":return e=t.data,e===du&&pu?null:e;default:return null}}function af(e,t){if(Wn)return e==="compositionend"||!Fa&&Mc(e,t)?(e=Tc(),Xo=Ia=an=null,Wn=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=gu(n)}}function Dc(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Dc(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function jc(){for(var e=window,t=li();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=li(e.document)}return t}function Da(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function hf(e){var t=jc(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Dc(n.ownerDocument.documentElement,n)){if(r!==null&&Da(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var o=n.textContent.length,i=Math.min(r.start,o);r=r.end===void 0?i:Math.min(r.end,o),!e.extend&&i>r&&(o=r,r=i,i=o),o=hu(n,i);var l=hu(n,r);o&&l&&(e.rangeCount!==1||e.anchorNode!==o.node||e.anchorOffset!==o.offset||e.focusNode!==l.node||e.focusOffset!==l.offset)&&(t=t.createRange(),t.setStart(o.node,o.offset),e.removeAllRanges(),i>r?(e.addRange(t),e.extend(l.node,l.offset)):(t.setEnd(l.node,l.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Gn=null,Xl=null,jr=null,Zl=!1;function yu(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Zl||Gn==null||Gn!==li(r)||(r=Gn,"selectionStart"in r&&Da(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),jr&&Zr(jr,r)||(jr=r,r=mi(Xl,"onSelect"),0qn||(e.current=oa[qn],oa[qn]=null,qn--)}function le(e,t){qn++,oa[qn]=e.current,e.current=t}var vn={},Be=xn(vn),Ze=xn(!1),Pn=vn;function ur(e,t){var n=e.type.contextTypes;if(!n)return vn;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var o={},i;for(i in n)o[i]=t[i];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=o),o}function Je(e){return e=e.childContextTypes,e!=null}function gi(){pe(Ze),pe(Be)}function _u(e,t,n){if(Be.current!==vn)throw Error(L(168));le(Be,t),le(Ze,n)}function Qc(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var o in r)if(!(o in t))throw Error(L(108,cm(e)||"Unknown",o));return xe({},n,r)}function hi(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||vn,Pn=Be.current,le(Be,e),le(Ze,Ze.current),!0}function Cu(e,t,n){var r=e.stateNode;if(!r)throw Error(L(169));n?(e=Qc(e,t,Pn),r.__reactInternalMemoizedMergedChildContext=e,pe(Ze),pe(Be),le(Be,e)):pe(Ze),le(Ze,n)}var Vt=null,Ii=!1,Sl=!1;function qc(e){Vt===null?Vt=[e]:Vt.push(e)}function _f(e){Ii=!0,qc(e)}function wn(){if(!Sl&&Vt!==null){Sl=!0;var e=0,t=ne;try{var n=Vt;for(ne=1;e>=l,o-=l,Bt=1<<32-zt(t)+o|n<z?(U=P,P=null):U=P.sibling;var F=g(p,P,h[z],w);if(F===null){P===null&&(P=U);break}e&&P&&F.alternate===null&&t(p,P),c=i(F,c,z),A===null?C=F:A.sibling=F,A=F,P=U}if(z===h.length)return n(p,P),fe&&Nn(p,z),C;if(P===null){for(;zz?(U=P,P=null):U=P.sibling;var ae=g(p,P,F.value,w);if(ae===null){P===null&&(P=U);break}e&&P&&ae.alternate===null&&t(p,P),c=i(ae,c,z),A===null?C=ae:A.sibling=ae,A=ae,P=U}if(F.done)return n(p,P),fe&&Nn(p,z),C;if(P===null){for(;!F.done;z++,F=h.next())F=y(p,F.value,w),F!==null&&(c=i(F,c,z),A===null?C=F:A.sibling=F,A=F);return fe&&Nn(p,z),C}for(P=r(p,P);!F.done;z++,F=h.next())F=f(P,p,z,F.value,w),F!==null&&(e&&F.alternate!==null&&P.delete(F.key===null?z:F.key),c=i(F,c,z),A===null?C=F:A.sibling=F,A=F);return e&&P.forEach(function(me){return t(p,me)}),fe&&Nn(p,z),C}function _(p,c,h,w){if(typeof h=="object"&&h!==null&&h.type===Kn&&h.key===null&&(h=h.props.children),typeof h=="object"&&h!==null){switch(h.$$typeof){case Po:e:{for(var C=h.key,A=c;A!==null;){if(A.key===C){if(C=h.type,C===Kn){if(A.tag===7){n(p,A.sibling),c=o(A,h.props.children),c.return=p,p=c;break e}}else if(A.elementType===C||typeof C=="object"&&C!==null&&C.$$typeof===nn&&Au(C)===A.type){n(p,A.sibling),c=o(A,h.props),c.ref=Lr(p,A,h),c.return=p,p=c;break e}n(p,A);break}else t(p,A);A=A.sibling}h.type===Kn?(c=An(h.props.children,p.mode,w,h.key),c.return=p,p=c):(w=ii(h.type,h.key,h.props,null,p.mode,w),w.ref=Lr(p,c,h),w.return=p,p=w)}return l(p);case Hn:e:{for(A=h.key;c!==null;){if(c.key===A)if(c.tag===4&&c.stateNode.containerInfo===h.containerInfo&&c.stateNode.implementation===h.implementation){n(p,c.sibling),c=o(c,h.children||[]),c.return=p,p=c;break e}else{n(p,c);break}else t(p,c);c=c.sibling}c=Pl(h,p.mode,w),c.return=p,p=c}return l(p);case nn:return A=h._init,_(p,c,A(h._payload),w)}if(Rr(h))return S(p,c,h,w);if(Nr(h))return b(p,c,h,w);Wo(p,h)}return typeof h=="string"&&h!==""||typeof h=="number"?(h=""+h,c!==null&&c.tag===6?(n(p,c.sibling),c=o(c,h),c.return=p,p=c):(n(p,c),c=Al(h,p.mode,w),c.return=p,p=c),l(p)):n(p,c)}return _}var dr=ed(!0),td=ed(!1),bi=xn(null),xi=null,Jn=null,Ba=null;function Ha(){Ba=Jn=xi=null}function Ka(e){var t=bi.current;pe(bi),e._currentValue=t}function aa(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function lr(e,t){xi=e,Ba=Jn=null,e=e.dependencies,e!==null&&e.firstContext!==null&&((e.lanes&t)!==0&&(Xe=!0),e.firstContext=null)}function yt(e){var t=e._currentValue;if(Ba!==e)if(e={context:e,memoizedValue:t,next:null},Jn===null){if(xi===null)throw Error(L(308));Jn=e,xi.dependencies={lanes:0,firstContext:e}}else Jn=Jn.next=e;return t}var Cn=null;function Wa(e){Cn===null?Cn=[e]:Cn.push(e)}function nd(e,t,n,r){var o=t.interleaved;return o===null?(n.next=n,Wa(t)):(n.next=o.next,o.next=n),t.interleaved=n,Yt(e,r)}function Yt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var rn=!1;function Ga(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function rd(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Kt(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function mn(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,(X&2)!==0){var o=r.pending;return o===null?t.next=t:(t.next=o.next,o.next=t),r.pending=t,Yt(e,n)}return o=r.interleaved,o===null?(t.next=t,Wa(r)):(t.next=o.next,o.next=t),r.interleaved=t,Yt(e,n)}function Jo(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Ta(e,n)}}function Pu(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var o=null,i=null;if(n=n.firstBaseUpdate,n!==null){do{var l={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};i===null?o=i=l:i=i.next=l,n=n.next}while(n!==null);i===null?o=i=t:i=i.next=t}else o=i=t;n={baseState:r.baseState,firstBaseUpdate:o,lastBaseUpdate:i,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function wi(e,t,n,r){var o=e.updateQueue;rn=!1;var i=o.firstBaseUpdate,l=o.lastBaseUpdate,s=o.shared.pending;if(s!==null){o.shared.pending=null;var u=s,d=u.next;u.next=null,l===null?i=d:l.next=d,l=u;var m=e.alternate;m!==null&&(m=m.updateQueue,s=m.lastBaseUpdate,s!==l&&(s===null?m.firstBaseUpdate=d:s.next=d,m.lastBaseUpdate=u))}if(i!==null){var y=o.baseState;l=0,m=d=u=null,s=i;do{var g=s.lane,f=s.eventTime;if((r&g)===g){m!==null&&(m=m.next={eventTime:f,lane:0,tag:s.tag,payload:s.payload,callback:s.callback,next:null});e:{var S=e,b=s;switch(g=t,f=n,b.tag){case 1:if(S=b.payload,typeof S=="function"){y=S.call(f,y,g);break e}y=S;break e;case 3:S.flags=S.flags&-65537|128;case 0:if(S=b.payload,g=typeof S=="function"?S.call(f,y,g):S,g==null)break e;y=xe({},y,g);break e;case 2:rn=!0}}s.callback!==null&&s.lane!==0&&(e.flags|=64,g=o.effects,g===null?o.effects=[s]:g.push(s))}else f={eventTime:f,lane:g,tag:s.tag,payload:s.payload,callback:s.callback,next:null},m===null?(d=m=f,u=y):m=m.next=f,l|=g;if(s=s.next,s===null){if(s=o.shared.pending,s===null)break;g=s,s=g.next,g.next=null,o.lastBaseUpdate=g,o.shared.pending=null}}while(!0);if(m===null&&(u=y),o.baseState=u,o.firstBaseUpdate=d,o.lastBaseUpdate=m,t=o.shared.interleaved,t!==null){o=t;do l|=o.lane,o=o.next;while(o!==t)}else i===null&&(o.shared.lanes=0);Mn|=l,e.lanes=l,e.memoizedState=y}}function Tu(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=El.transition;El.transition={};try{e(!1),t()}finally{ne=n,El.transition=r}}function xd(){return vt().memoizedState}function Af(e,t,n){var r=gn(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},wd(e))kd(t,n);else if(n=nd(e,t,n,r),n!==null){var o=Ye();At(n,e,r,o),Sd(n,t,r)}}function Pf(e,t,n){var r=gn(e),o={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(wd(e))kd(t,o);else{var i=e.alternate;if(e.lanes===0&&(i===null||i.lanes===0)&&(i=t.lastRenderedReducer,i!==null))try{var l=t.lastRenderedState,s=i(l,n);if(o.hasEagerState=!0,o.eagerState=s,Pt(s,l)){var u=t.interleaved;u===null?(o.next=o,Wa(t)):(o.next=u.next,u.next=o),t.interleaved=o;return}}catch{}finally{}n=nd(e,t,o,r),n!==null&&(o=Ye(),At(n,e,r,o),Sd(n,t,r))}}function wd(e){var t=e.alternate;return e===be||t!==null&&t===be}function kd(e,t){Ur=Si=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Sd(e,t,n){if((n&4194240)!==0){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Ta(e,n)}}var Ni={readContext:yt,useCallback:je,useContext:je,useEffect:je,useImperativeHandle:je,useInsertionEffect:je,useLayoutEffect:je,useMemo:je,useReducer:je,useRef:je,useState:je,useDebugValue:je,useDeferredValue:je,useTransition:je,useMutableSource:je,useSyncExternalStore:je,useId:je,unstable_isNewReconciler:!1},Tf={readContext:yt,useCallback:function(e,t){return Mt().memoizedState=[e,t===void 0?null:t],e},useContext:yt,useEffect:Mu,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,ti(4194308,4,gd.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ti(4194308,4,e,t)},useInsertionEffect:function(e,t){return ti(4,2,e,t)},useMemo:function(e,t){var n=Mt();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=Mt();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=Af.bind(null,be,e),[r.memoizedState,e]},useRef:function(e){var t=Mt();return e={current:e},t.memoizedState=e},useState:Ru,useDebugValue:ts,useDeferredValue:function(e){return Mt().memoizedState=e},useTransition:function(){var e=Ru(!1),t=e[0];return e=zf.bind(null,e[1]),Mt().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=be,o=Mt();if(fe){if(n===void 0)throw Error(L(407));n=n()}else{if(n=t(),Te===null)throw Error(L(349));(Rn&30)!==0||ad(r,t,n)}o.memoizedState=n;var i={value:n,getSnapshot:t};return o.queue=i,Mu(ud.bind(null,r,i,e),[e]),r.flags|=2048,lo(9,sd.bind(null,r,i,n,t),void 0,null),n},useId:function(){var e=Mt(),t=Te.identifierPrefix;if(fe){var n=Ht,r=Bt;n=(r&~(1<<32-zt(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=oo++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=l.createElement(n,{is:r.is}):(e=l.createElement(n),n==="select"&&(l=e,r.multiple?l.multiple=!0:r.size&&(l.size=r.size))):e=l.createElementNS(e,n),e[It]=t,e[to]=r,Rd(e,t,!1,!1),t.stateNode=e;e:{switch(l=Bl(n,r),n){case"dialog":de("cancel",e),de("close",e),o=r;break;case"iframe":case"object":case"embed":de("load",e),o=r;break;case"video":case"audio":for(o=0;ofr&&(t.flags|=128,r=!0,zr(i,!1),t.lanes=4194304)}else{if(!r)if(e=ki(l),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),zr(i,!0),i.tail===null&&i.tailMode==="hidden"&&!l.alternate&&!fe)return Ue(t),null}else 2*ke()-i.renderingStartTime>fr&&n!==1073741824&&(t.flags|=128,r=!0,zr(i,!1),t.lanes=4194304);i.isBackwards?(l.sibling=t.child,t.child=l):(n=i.last,n!==null?n.sibling=l:t.child=l,i.last=l)}return i.tail!==null?(t=i.tail,i.rendering=t,i.tail=t.sibling,i.renderingStartTime=ke(),t.sibling=null,n=ve.current,le(ve,r?n&1|2:n&1),t):(Ue(t),null);case 22:case 23:return as(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&(t.mode&1)!==0?(tt&1073741824)!==0&&(Ue(t),t.subtreeFlags&6&&(t.flags|=8192)):Ue(t),null;case 24:return null;case 25:return null}throw Error(L(156,t.tag))}function jf(e,t){switch(Ua(t),t.tag){case 1:return Je(t.type)&&gi(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return pr(),pe(Ze),pe(Be),qa(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 5:return Qa(t),null;case 13:if(pe(ve),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(L(340));cr()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return pe(ve),null;case 4:return pr(),null;case 10:return Ka(t.type._context),null;case 22:case 23:return as(),null;case 24:return null;default:return null}}var Yo=!1,Ve=!1,Uf=typeof WeakSet=="function"?WeakSet:Set,O=null;function er(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){we(e,t,r)}else n.current=null}function ha(e,t,n){try{n()}catch(r){we(e,t,r)}}var Ku=!1;function Vf(e,t){if(Jl=di,e=jc(),Da(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var o=r.anchorOffset,i=r.focusNode;r=r.focusOffset;try{n.nodeType,i.nodeType}catch{n=null;break e}var l=0,s=-1,u=-1,d=0,m=0,y=e,g=null;t:for(;;){for(var f;y!==n||o!==0&&y.nodeType!==3||(s=l+o),y!==i||r!==0&&y.nodeType!==3||(u=l+r),y.nodeType===3&&(l+=y.nodeValue.length),(f=y.firstChild)!==null;)g=y,y=f;for(;;){if(y===e)break t;if(g===n&&++d===o&&(s=l),g===i&&++m===r&&(u=l),(f=y.nextSibling)!==null)break;y=g,g=y.parentNode}y=f}n=s===-1||u===-1?null:{start:s,end:u}}else n=null}n=n||{start:0,end:0}}else n=null;for(ea={focusedElem:e,selectionRange:n},di=!1,O=t;O!==null;)if(t=O,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,O=e;else for(;O!==null;){t=O;try{var S=t.alternate;if((t.flags&1024)!==0)switch(t.tag){case 0:case 11:case 15:break;case 1:if(S!==null){var b=S.memoizedProps,_=S.memoizedState,p=t.stateNode,c=p.getSnapshotBeforeUpdate(t.elementType===t.type?b:_t(t.type,b),_);p.__reactInternalSnapshotBeforeUpdate=c}break;case 3:var h=t.stateNode.containerInfo;h.nodeType===1?h.textContent="":h.nodeType===9&&h.documentElement&&h.removeChild(h.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(L(163))}}catch(w){we(t,t.return,w)}if(e=t.sibling,e!==null){e.return=t.return,O=e;break}O=t.return}return S=Ku,Ku=!1,S}function Vr(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var o=r=r.next;do{if((o.tag&e)===e){var i=o.destroy;o.destroy=void 0,i!==void 0&&ha(t,n,i)}o=o.next}while(o!==r)}}function Fi(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function ya(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function Od(e){var t=e.alternate;t!==null&&(e.alternate=null,Od(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[It],delete t[to],delete t[ra],delete t[Nf],delete t[Ef])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function $d(e){return e.tag===5||e.tag===3||e.tag===4}function Wu(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||$d(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function va(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=fi));else if(r!==4&&(e=e.child,e!==null))for(va(e,t,n),e=e.sibling;e!==null;)va(e,t,n),e=e.sibling}function ba(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(ba(e,t,n),e=e.sibling;e!==null;)ba(e,t,n),e=e.sibling}var Oe=null,Ct=!1;function tn(e,t,n){for(n=n.child;n!==null;)Fd(e,t,n),n=n.sibling}function Fd(e,t,n){if(Ot&&typeof Ot.onCommitFiberUnmount=="function")try{Ot.onCommitFiberUnmount(Ai,n)}catch{}switch(n.tag){case 5:Ve||er(n,t);case 6:var r=Oe,o=Ct;Oe=null,tn(e,t,n),Oe=r,Ct=o,Oe!==null&&(Ct?(e=Oe,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):Oe.removeChild(n.stateNode));break;case 18:Oe!==null&&(Ct?(e=Oe,n=n.stateNode,e.nodeType===8?kl(e.parentNode,n):e.nodeType===1&&kl(e,n),qr(e)):kl(Oe,n.stateNode));break;case 4:r=Oe,o=Ct,Oe=n.stateNode.containerInfo,Ct=!0,tn(e,t,n),Oe=r,Ct=o;break;case 0:case 11:case 14:case 15:if(!Ve&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){o=r=r.next;do{var i=o,l=i.destroy;i=i.tag,l!==void 0&&((i&2)!==0||(i&4)!==0)&&ha(n,t,l),o=o.next}while(o!==r)}tn(e,t,n);break;case 1:if(!Ve&&(er(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(s){we(n,t,s)}tn(e,t,n);break;case 21:tn(e,t,n);break;case 22:n.mode&1?(Ve=(r=Ve)||n.memoizedState!==null,tn(e,t,n),Ve=r):tn(e,t,n);break;default:tn(e,t,n)}}function Gu(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Uf),t.forEach(function(r){var o=Xf.bind(null,e,r);n.has(r)||(n.add(r),r.then(o,o))})}}function Et(e,t){var n=t.deletions;if(n!==null)for(var r=0;ro&&(o=l),r&=~i}if(r=o,r=ke()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Hf(r/1960))-r,10e?16:e,sn===null)var r=!1;else{if(e=sn,sn=null,Ci=0,(X&6)!==0)throw Error(L(331));var o=X;for(X|=4,O=e.current;O!==null;){var i=O,l=i.child;if((O.flags&16)!==0){var s=i.deletions;if(s!==null){for(var u=0;uke()-is?zn(e,0):os|=n),et(e,t)}function Wd(e,t){t===0&&((e.mode&1)===0?t=1:(t=Io,Io<<=1,(Io&130023424)===0&&(Io=4194304)));var n=Ye();e=Yt(e,t),e!==null&&(so(e,t,n),et(e,n))}function qf(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Wd(e,n)}function Xf(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,o=e.memoizedState;o!==null&&(n=o.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(L(314))}r!==null&&r.delete(t),Wd(e,n)}var Gd;Gd=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||Ze.current)Xe=!0;else{if((e.lanes&n)===0&&(t.flags&128)===0)return Xe=!1,Ff(e,t,n);Xe=(e.flags&131072)!==0}else Xe=!1,fe&&(t.flags&1048576)!==0&&Xc(t,vi,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;ni(e,t),e=t.pendingProps;var o=ur(t,Be.current);lr(t,n),o=Za(null,t,r,e,o,n);var i=Ja();return t.flags|=1,typeof o=="object"&&o!==null&&typeof o.render=="function"&&o.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,Je(r)?(i=!0,hi(t)):i=!1,t.memoizedState=o.state!==null&&o.state!==void 0?o.state:null,Ga(t),o.updater=$i,t.stateNode=o,o._reactInternals=t,ua(t,r,e,n),t=pa(null,t,r,!0,i,n)):(t.tag=0,fe&&i&&ja(t),Ge(null,t,o,n),t=t.child),t;case 16:r=t.elementType;e:{switch(ni(e,t),e=t.pendingProps,o=r._init,r=o(r._payload),t.type=r,o=t.tag=Jf(r),e=_t(r,e),o){case 0:t=da(null,t,r,e,n);break e;case 1:t=Vu(null,t,r,e,n);break e;case 11:t=ju(null,t,r,e,n);break e;case 14:t=Uu(null,t,r,_t(r.type,e),n);break e}throw Error(L(306,r,""))}return t;case 0:return r=t.type,o=t.pendingProps,o=t.elementType===r?o:_t(r,o),da(e,t,r,o,n);case 1:return r=t.type,o=t.pendingProps,o=t.elementType===r?o:_t(r,o),Vu(e,t,r,o,n);case 3:e:{if(Ad(t),e===null)throw Error(L(387));r=t.pendingProps,i=t.memoizedState,o=i.element,rd(e,t),wi(t,r,null,n);var l=t.memoizedState;if(r=l.element,i.isDehydrated)if(i={element:r,isDehydrated:!1,cache:l.cache,pendingSuspenseBoundaries:l.pendingSuspenseBoundaries,transitions:l.transitions},t.updateQueue.baseState=i,t.memoizedState=i,t.flags&256){o=mr(Error(L(423)),t),t=Bu(e,t,r,n,o);break e}else if(r!==o){o=mr(Error(L(424)),t),t=Bu(e,t,r,n,o);break e}else for(nt=pn(t.stateNode.containerInfo.firstChild),rt=t,fe=!0,Lt=null,n=td(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(cr(),r===o){t=Qt(e,t,n);break e}Ge(e,t,r,n)}t=t.child}return t;case 5:return od(t),e===null&&la(t),r=t.type,o=t.pendingProps,i=e!==null?e.memoizedProps:null,l=o.children,ta(r,o)?l=null:i!==null&&ta(r,i)&&(t.flags|=32),zd(e,t),Ge(e,t,l,n),t.child;case 6:return e===null&&la(t),null;case 13:return Pd(e,t,n);case 4:return Ya(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=dr(t,null,r,n):Ge(e,t,r,n),t.child;case 11:return r=t.type,o=t.pendingProps,o=t.elementType===r?o:_t(r,o),ju(e,t,r,o,n);case 7:return Ge(e,t,t.pendingProps,n),t.child;case 8:return Ge(e,t,t.pendingProps.children,n),t.child;case 12:return Ge(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,o=t.pendingProps,i=t.memoizedProps,l=o.value,le(bi,r._currentValue),r._currentValue=l,i!==null)if(Pt(i.value,l)){if(i.children===o.children&&!Ze.current){t=Qt(e,t,n);break e}}else for(i=t.child,i!==null&&(i.return=t);i!==null;){var s=i.dependencies;if(s!==null){l=i.child;for(var u=s.firstContext;u!==null;){if(u.context===r){if(i.tag===1){u=Kt(-1,n&-n),u.tag=2;var d=i.updateQueue;if(d!==null){d=d.shared;var m=d.pending;m===null?u.next=u:(u.next=m.next,m.next=u),d.pending=u}}i.lanes|=n,u=i.alternate,u!==null&&(u.lanes|=n),aa(i.return,n,t),s.lanes|=n;break}u=u.next}}else if(i.tag===10)l=i.type===t.type?null:i.child;else if(i.tag===18){if(l=i.return,l===null)throw Error(L(341));l.lanes|=n,s=l.alternate,s!==null&&(s.lanes|=n),aa(l,n,t),l=i.sibling}else l=i.child;if(l!==null)l.return=i;else for(l=i;l!==null;){if(l===t){l=null;break}if(i=l.sibling,i!==null){i.return=l.return,l=i;break}l=l.return}i=l}Ge(e,t,o.children,n),t=t.child}return t;case 9:return o=t.type,r=t.pendingProps.children,lr(t,n),o=yt(o),r=r(o),t.flags|=1,Ge(e,t,r,n),t.child;case 14:return r=t.type,o=_t(r,t.pendingProps),o=_t(r.type,o),Uu(e,t,r,o,n);case 15:return Cd(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,o=t.pendingProps,o=t.elementType===r?o:_t(r,o),ni(e,t),t.tag=1,Je(r)?(e=!0,hi(t)):e=!1,lr(t,n),Nd(t,r,o),ua(t,r,o,n),pa(null,t,r,!0,e,n);case 19:return Td(e,t,n);case 22:return Ld(e,t,n)}throw Error(L(156,t.tag))};function Yd(e,t){return wc(e,t)}function Zf(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function gt(e,t,n,r){return new Zf(e,t,n,r)}function us(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Jf(e){if(typeof e=="function")return us(e)?1:0;if(e!=null){if(e=e.$$typeof,e===La)return 11;if(e===za)return 14}return 2}function hn(e,t){var n=e.alternate;return n===null?(n=gt(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function ii(e,t,n,r,o,i){var l=2;if(r=e,typeof e=="function")us(e)&&(l=1);else if(typeof e=="string")l=5;else e:switch(e){case Kn:return An(n.children,o,i,t);case Ca:l=8,o|=8;break;case Rl:return e=gt(12,n,t,o|2),e.elementType=Rl,e.lanes=i,e;case Ml:return e=gt(13,n,t,o),e.elementType=Ml,e.lanes=i,e;case Il:return e=gt(19,n,t,o),e.elementType=Il,e.lanes=i,e;case oc:return ji(n,o,i,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case nc:l=10;break e;case rc:l=9;break e;case La:l=11;break e;case za:l=14;break e;case nn:l=16,r=null;break e}throw Error(L(130,e==null?e:typeof e,""))}return t=gt(l,n,t,o),t.elementType=e,t.type=r,t.lanes=i,t}function An(e,t,n,r){return e=gt(7,e,r,t),e.lanes=n,e}function ji(e,t,n,r){return e=gt(22,e,r,t),e.elementType=oc,e.lanes=n,e.stateNode={isHidden:!1},e}function Al(e,t,n){return e=gt(6,e,null,t),e.lanes=n,e}function Pl(e,t,n){return t=gt(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function eg(e,t,n,r,o){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=fl(0),this.expirationTimes=fl(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=fl(0),this.identifierPrefix=r,this.onRecoverableError=o,this.mutableSourceEagerHydrationData=null}function cs(e,t,n,r,o,i,l,s,u){return e=new eg(e,t,n,s,u),t===1?(t=1,i===!0&&(t|=8)):t=0,i=gt(3,null,null,t),e.current=i,i.stateNode=e,i.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Ga(i),e}function tg(e,t,n){var r=3{"use strict";function Jd(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Jd)}catch(e){console.error(e)}}Jd(),ep.exports=Zd()});var rp=jt(fs=>{"use strict";var np=tp();fs.createRoot=np.createRoot,fs.hydrateRoot=np.hydrateRoot;var _h});var ip=jt(Ki=>{"use strict";var lg=Eo(),ag=Symbol.for("react.element"),sg=Symbol.for("react.fragment"),ug=Object.prototype.hasOwnProperty,cg=lg.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,dg={key:!0,ref:!0,__self:!0,__source:!0};function op(e,t,n){var r,o={},i=null,l=null;n!==void 0&&(i=""+n),t.key!==void 0&&(i=""+t.key),t.ref!==void 0&&(l=t.ref);for(r in t)ug.call(t,r)&&!dg.hasOwnProperty(r)&&(o[r]=t[r]);if(e&&e.defaultProps)for(r in t=e.defaultProps,t)o[r]===void 0&&(o[r]=t[r]);return{$$typeof:ag,type:e,key:i,ref:l,props:o,_owner:cg.current}}Ki.Fragment=sg;Ki.jsx=op;Ki.jsxs=op});var gs=jt((Ph,lp)=>{"use strict";lp.exports=ip()});var k=wo(Eo(),1),Sp=wo(rp(),1);var a=wo(gs(),1),Wi=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,pg="http://localhost:8080",Np="https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl",ap=["localhost","k3s_localhost","k3s_vps"],mg={localhost:"LOCALHOST",k3s_localhost:"K3S@LOCALHOST",k3s_vps:"K3S@VPS"};function sp(e){try{let t=new URL(String(e??""));return["http:","https:"].includes(t.protocol)?(t.search="",t.hash="",t.href.replace(/\/$/,"")):""}catch{return""}}function fg(){return window.location.pathname.split("/").filter(Boolean).find(e=>Wi.test(e))?.toLowerCase()??""}function gg(){let e=new URL(Np).host;return window.location.host===e?"k3s_vps":["localhost","127.0.0.1","::1"].includes(window.location.hostname)&&window.location.port==="8080"?"localhost":"k3s_localhost"}function hg(){let e=window.__STEM_CARD_RUNTIME__,t=ap.includes(e?.active_source)?e?.active_source:gg(),n=new Map(Array.isArray(e?.sources)?e.sources.map(o=>[o?.id,o]):[]),r=ap.map(o=>{let i=n.get(o),l=o==="localhost"?t===o?window.location.origin:pg:o==="k3s_vps"?Np:t===o?window.location.origin:"",s=sp(i?.base_url)||sp(l),u=t===o;return{id:o,label:String(i?.label||mg[o]),base_url:s,available:u||!!s&&i?.available!==!1,reason:String(i?.reason||(s?"":"\u0179r\xF3d\u0142o nie ma skonfigurowanego adresu"))}});return{schema:"stem.card-runtime/v1",default_source:"localhost",active_source:t,current_resource_uuid:String(e?.current_resource_uuid||fg()).toLowerCase(),sources:r}}function Ep(e,t,n=""){return!e.available||!e.base_url||!Wi.test(t)?"":`${e.base_url}/${t.toLowerCase()}/${n}`}function yg(e){let t=String(e.resource_uuid??"").toLowerCase();if(Wi.test(t))return t;for(let n of[e.local_href,e.href])try{let r=new URL(String(n??""),window.location.href).pathname.split("/").filter(Boolean).find(o=>Wi.test(o));if(r)return r.toLowerCase()}catch{}return""}function vg(e,t){return Ep(t,yg(e))||e.local_href||e.href}var _p={hidden:"UKRYTY",connecting:"\u0141\u0104CZENIE",connected:"ONLINE \xB7 LIVE",replaying:"ONLINE \xB7 REPLAY",verifying:"ONLINE \xB7 WERYFIKACJA",ready:"ONLINE \xB7 GOTOWY",failed:"ONLINE \xB7 B\u0141\u0104D STANU",offline:"OFFLINE \xB7 ZAPIS",unselected:"OFFLINE \xB7 BRAK CELU"},up=.25,Dn=1,cp=4;function bg(e){let t=window.getComputedStyle(e).overflowY;return t==="auto"||t==="scroll"||t==="hidden"||t==="clip"}function Cp(e){let t=e.getBoundingClientRect(),n=e.offsetHeight||e.clientHeight,r=n>0?t.height/n:1,o=t.top+e.clientTop*r;return{top:o,bottom:o+e.clientHeight*r}}function Lp(e){let t=document.querySelector(".viewer-chrome");if(t?.classList.contains("is-nvim-fit"))return null;let n=0,r=window.innerHeight;if(t){let o=t.getBoundingClientRect(),i=e.getBoundingClientRect();o.lefti.left&&o.bottom>0&&o.topDn?{top:n,bottom:r}:null}function xg(e){let t=Lp(e);if(!t)return null;let{top:n,bottom:r}=t;for(let o=e.parentElement;o&&o!==document.body&&o!==document.documentElement;o=o.parentElement){if(!bg(o))continue;let i=Cp(o);n=Math.max(n,i.top),r=Math.min(r,i.bottom)}return r-n>Dn?{top:n,bottom:r}:null}function Qi(){return document.scrollingElement??document.documentElement}function wg(e){let t=[];for(let r=e.parentElement;r&&r!==document.body&&r!==document.documentElement;r=r.parentElement){let o=window.getComputedStyle(r).overflowY;(o==="auto"||o==="scroll")&&r.scrollHeight-r.clientHeight>1&&t.push(r)}let n=Qi();return t.includes(n)||t.push(n),t}function kg(e){if(e===Qi())return 1;let t=e.getBoundingClientRect(),n=e.offsetHeight||e.clientHeight,r=n>0?t.height/n:1;return Number.isFinite(r)&&r>0?r:1}function Gi(e,t){let n=e.style.scrollBehavior;e.style.scrollBehavior="auto",t.left!=null&&(e.scrollLeft=t.left),t.top!=null&&(e.scrollTop=t.top),e.style.scrollBehavior=n}function Sg(e,t){let n=e.closest(".sheet-content");if(!n)return 0;let r=Cp(n),o=0;if(r.bottom<=t.top?o=r.bottom-t.bottom:r.top>=t.bottom&&(o=r.top-t.top),Math.abs(o)<=Dn)return 0;let i=Qi(),l=i.scrollTop,s=Math.max(0,i.scrollHeight-i.clientHeight);return Gi(i,{top:Math.max(0,Math.min(s,l+o))}),i.scrollTop-l}function dp(e,t){let n=t;for(let r of wg(e)){if(Math.abs(n)<=Dn)break;let o=kg(r),i=Math.max(0,r.scrollHeight-r.clientHeight),l=r.scrollTop,s=Math.max(0,Math.min(i,l+n/o));Gi(r,{top:s}),n-=(r.scrollTop-l)*o}return t-n}function pp(e,t){let n=e.getBoundingClientRect(),r=(n.top+n.bottom)/2,o=t.bottom-t.top,i=t.top+o*up,l=t.bottom-o*up;return rl?r-l:0}var Ft=({html:e,className:t})=>(0,a.jsx)("div",{className:t,dangerouslySetInnerHTML:{__html:e}});function zp(e,t){e.setAttribute("preserveAspectRatio","xMinYMin meet"),t&&(e.setAttribute("viewBox",`${t.x} ${t.y} ${t.width} ${t.height}`),e.setAttribute("width",`${t.width}px`),e.setAttribute("height",`${t.height}px`),e.dataset.tileIndex=String(t.index),e.dataset.tileRow=String(t.row),e.dataset.tileColumn=String(t.column))}function Ng(e){return e.normalize("NFKD").replace(/[\u0300-\u036f]/g,"").toLowerCase().replace(/[^a-z0-9_.:-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,120)||"uml-element"}function ys(e){let t=e;if(typeof t.getBBox!="function")return null;try{let n=t.getBBox();return[n.x,n.y,n.width,n.height].every(Number.isFinite)?{x:n.x,y:n.y,width:n.width,height:n.height}:null}catch{return null}}function Eg(e){let t=ys(e);if(!t)return null;let n=Number(e.dataset.umlLayoutOriginX),r=Number(e.dataset.umlLayoutOriginY),o=Number(e.dataset.umlLayoutDx),i=Number(e.dataset.umlLayoutDy),l=Number(e.dataset.umlLayoutSx),s=Number(e.dataset.umlLayoutSy);return[n,r,o,i,l,s].every(Number.isFinite)?{x:n+(t.x-n)*l+o,y:r+(t.y-r)*s+i,width:t.width*l,height:t.height*s}:t}function Ap(e){e.dataset.umlLayoutRemembered!=="true"&&(e.dataset.umlLayoutRemembered="true",e.dataset.umlLayoutOriginalTransform=e.getAttribute("transform")??"",e.dataset.umlLayoutOriginalVisibility=e.getAttribute("visibility")??"",e.dataset.umlLayoutOriginalStyle=e.getAttribute("style")??"",e instanceof SVGTextElement&&(e.dataset.umlLayoutOriginalText=e.textContent??"",e.dataset.umlLayoutOriginalX=e.getAttribute("x")??"",e.dataset.umlLayoutOriginalTextAnchor=e.getAttribute("text-anchor")??"",e.dataset.umlLayoutOriginalFontFamily=e.getAttribute("font-family")??"",e.dataset.umlLayoutOriginalFontSize=e.getAttribute("font-size")??"",e.dataset.umlLayoutOriginalFontWeight=e.getAttribute("font-weight")??""),e instanceof SVGRectElement&&(e.dataset.umlLayoutOriginalFill=e.getAttribute("fill")??"",e.dataset.umlLayoutOriginalStroke=e.getAttribute("stroke")??"",e.dataset.umlLayoutOriginalStrokeWidth=e.getAttribute("stroke-width")??""))}function _g(e){e.querySelectorAll(".uml-layout-hit,.uml-layout-resize,.uml-layout-generated").forEach(t=>t.remove()),e.querySelectorAll("[data-uml-layout-remembered='true']").forEach(t=>{let n=(r,o)=>{o?t.setAttribute(r,o):t.removeAttribute(r)};n("transform",t.dataset.umlLayoutOriginalTransform),n("visibility",t.dataset.umlLayoutOriginalVisibility),n("style",t.dataset.umlLayoutOriginalStyle),t instanceof SVGTextElement&&(t.textContent=t.dataset.umlLayoutOriginalText??"",n("x",t.dataset.umlLayoutOriginalX),n("text-anchor",t.dataset.umlLayoutOriginalTextAnchor),n("font-family",t.dataset.umlLayoutOriginalFontFamily),n("font-size",t.dataset.umlLayoutOriginalFontSize),n("font-weight",t.dataset.umlLayoutOriginalFontWeight)),t instanceof SVGRectElement&&(n("fill",t.dataset.umlLayoutOriginalFill),n("stroke",t.dataset.umlLayoutOriginalStroke),n("stroke-width",t.dataset.umlLayoutOriginalStrokeWidth)),delete t.dataset.umlLayoutOriginX,delete t.dataset.umlLayoutOriginY,delete t.dataset.umlLayoutDx,delete t.dataset.umlLayoutDy,delete t.dataset.umlLayoutSx,delete t.dataset.umlLayoutSy,delete t.dataset.umlLayoutId})}function Cg(e,t){let n=e.querySelector(":scope > g")??e,r=Array.from(n.children).filter(s=>s instanceof SVGElement),o=r.filter(s=>s instanceof SVGRectElement).map(s=>({rect:s,box:ys(s)})).filter(s=>!!(s.box&&s.box.width>=30&&s.box.height>=14)).sort((s,u)=>s.box.width*s.box.height-u.box.width*u.box.height),i=new Map;for(let s of r){let u=ys(s);if(!u)continue;let d=u.x+u.width/2,m=u.y+u.height/2,y=o.find(({box:g})=>d>=g.x-.5&&d<=g.x+g.width+.5&&m>=g.y-.5&&m<=g.y+g.height+.5);y&&i.set(s,y.rect)}let l=new Set;return o.map(({rect:s,box:u},d)=>{let m=r.filter(c=>i.get(c)===s);m.includes(s)||m.unshift(s);let y=m.filter(c=>c instanceof SVGTextElement).sort((c,h)=>Number(c.getAttribute("y"))-Number(h.getAttribute("y"))),g=(y[0]?.textContent??s.id??`element-${d+1}`).trim(),f=/^(\d{1,3})\b/.exec(g)?.[1]?.padStart(2,"0"),S=s.id||(f?t.get(f):"")||g,b=Ng(S),_=b,p=2;for(;l.has(_);)_=`${b}-${p++}`;return l.add(_),m.forEach(Ap),{id:_,box:u,rect:s,members:m,texts:y}})}function mo(e){let t=e.texts[0],n=e.rect.getAttribute("style")??"",r=i=>new RegExp(`${i}\\s*:\\s*([^;]+)`,"i").exec(n)?.[1]?.trim(),o=Number(t?.getAttribute("font-size")??12);return{...e.box,z:0,scale:1,rotation:0,text:{title:(e.texts[0]?.textContent??"").trim(),description:e.texts.slice(1).map(i=>(i.textContent??"").trim()).join(` +`),lines:e.texts.map(i=>(i.textContent??"").trim()),font_family:t?.getAttribute("font-family")??"Monospace",font_size:Number.isFinite(o)?o:12,font_weight:Number(t?.getAttribute("font-weight")??400)||400,align:"left"},style:{fill:e.rect.getAttribute("fill")??r("fill")??"#edf6fa",stroke:e.rect.getAttribute("stroke")??r("stroke")??"#2c7794",stroke_width:Number(e.rect.getAttribute("stroke-width")??r("stroke-width")??1.5)}}}function Lg(e,t){let n=Math.max(12,Number(t.width)||e.box.width),r=Math.max(12,Number(t.height)||e.box.height),o=Number.isFinite(Number(t.x))?Number(t.x):e.box.x,i=Number.isFinite(Number(t.y))?Number(t.y):e.box.y,l=n/Math.max(1,e.box.width),s=r/Math.max(1,e.box.height),u=o-e.box.x,d=i-e.box.y,m=`translate(${u} ${d}) translate(${e.box.x} ${e.box.y}) scale(${l} ${s}) translate(${-e.box.x} ${-e.box.y})`;e.members.forEach(f=>{Ap(f);let S=f.dataset.umlLayoutOriginalTransform??"";f.setAttribute("transform",`${m}${S?` ${S}`:""}`),f.dataset.umlLayoutOriginX=String(e.box.x),f.dataset.umlLayoutOriginY=String(e.box.y),f.dataset.umlLayoutDx=String(u),f.dataset.umlLayoutDy=String(d),f.dataset.umlLayoutSx=String(l),f.dataset.umlLayoutSy=String(s),f.dataset.umlLayoutId=e.id});let y=t.text??{},g=y.lines??[y.title??"",...y.description?y.description.split(` +`):[]];e.texts.forEach((f,S)=>{S(e.tile?.page_height??0)?"landscape":"portrait",scale:1},elements:{},relations:{}}}function vs(e){if(!e)return{};let t=e.count===1,n=t?Math.max(0,(e.page_width-e.width)/2):e.column===1&&e.columns>1?e.page_width-e.width:e.column===e.columns?0:Math.max(0,(e.page_width-e.width)/2),r=t?0:e.row===1&&e.rows>1?e.page_height-e.height:e.row===e.rows?0:Math.max(0,(e.page_height-e.height)/2);return{"--uml-tile-page-width":`${e.page_width}px`,"--uml-tile-page-height":`${t?e.height:e.page_height}px`,"--uml-tile-left":`${n}px`,"--uml-tile-top":`${r}px`}}function zg({html:e}){return(0,a.jsx)(Ft,{html:e})}function Ag({html:e=""}){return e?(0,a.jsx)(Ft,{html:e}):null}function Pg(e){let t=e.spread_count??1;if(t<=1)return null;let n=Math.max(1,Math.min(t,e.spread_index??1)),r=Math.max(1,Math.min(t,e.spread_columns??1)),o=n-1,i=Math.floor(o/r),l=o%r;return{left:l>0,right:l+10,down:n+r<=t,index:n,count:t}}function Pp({continuation:e}){if(!e)return null;let t=[e.left&&["left","\u2190","kontynuacja po lewej"],e.up&&["up","\u2191","kontynuacja powy\u017Cej"],e.down&&["down","\u2193","kontynuacja poni\u017Cej"],e.right&&["right","\u2192","kontynuacja po prawej"]].filter(Boolean);return(0,a.jsx)("span",{className:"uml-page-continuation","aria-label":`Logiczny diagram: strona ${e.index} z ${e.count}`,title:`Ten sam diagram \xB7 strona ${e.index}/${e.count}`,children:t.map(([n,r,o])=>(0,a.jsx)("i",{"data-direction":n,"aria-label":o,children:r},n))})}function mp({side:e,html:t}){return(0,a.jsx)("aside",{className:`pdf-margin ${e}`,dangerouslySetInnerHTML:{__html:t}})}function bs({header:e,footer:t="",left:n="",right:r="",children:o,className:i=""}){return(0,a.jsx)("article",{className:`sheet ${i}`,children:(0,a.jsxs)("div",{className:"sheet-content",children:[(0,a.jsx)(zg,{html:e}),(0,a.jsx)(mp,{side:"left",html:n}),(0,a.jsx)("section",{className:"pdf-main card-section",children:o}),(0,a.jsx)(mp,{side:"right",html:r}),(0,a.jsx)(Ag,{html:t})]})})}function Tg({viewpoints:e}){let t=e.filter(i=>i.status==="enabled"&&i.target_id),[n,r]=(0,k.useState)(t[0]?.id??"");(0,k.useEffect)(()=>{if(!t.length)return;let i=0,l=()=>{window.cancelAnimationFrame(i),i=window.requestAnimationFrame(()=>{let s=document.querySelector(".viewer-chrome")?.getBoundingClientRect().bottom??0,u=t.map(m=>({viewpoint:m,element:document.getElementById(m.target_id)})).filter(m=>!!m.element);if(!u.length)return;let d=u.reduce((m,y)=>{let g=Math.abs(y.element.getBoundingClientRect().top-s-18);return g{window.cancelAnimationFrame(i),window.removeEventListener("scroll",l),window.removeEventListener("resize",l)}},[e]);let o=i=>{if(i.status!=="enabled"||!i.target_id)return;let l=document.getElementById(i.target_id);if(!l)return;let u=document.querySelector(".viewer-chrome")?.getBoundingClientRect().height??0;r(i.id),window.scrollTo({top:Math.max(0,window.scrollY+l.getBoundingClientRect().top-u-8),behavior:"smooth"})};return(0,a.jsx)("nav",{className:"viewpoint-bar","aria-label":"Perspektywy architektury A1\u2013A8",children:e.map(i=>{let l=i.status==="unavailable";return(0,a.jsxs)("button",{type:"button",className:l?"is-unavailable":"","aria-current":!l&&n===i.id?"location":void 0,disabled:l,title:l?i.reason:i.subtitle,onClick:()=>o(i),children:[(0,a.jsx)("span",{children:i.id}),(0,a.jsx)("strong",{children:i.label}),l&&(0,a.jsx)("small",{children:"N/D"})]},i.id)})})}function Rg({runtime:e,onSelect:t}){let n=e.sources.find(r=>r.id===e.active_source);return(0,a.jsxs)("label",{className:"runtime-source-selector","data-stem-source-selector":"true",title:n?.base_url||n?.reason||"Wyb\xF3r \u017Ar\xF3d\u0142a aplikacji",children:[(0,a.jsx)("span",{children:"\u0179R\xD3D\u0141O"}),(0,a.jsx)("select",{"aria-label":"\u0179r\xF3d\u0142o aplikacji",value:e.active_source,onChange:r=>t(r.currentTarget.value),children:e.sources.map(r=>(0,a.jsxs)("option",{value:r.id,disabled:!r.available,children:[r.label,r.available?"":" \xB7 BRAK"]},r.id))})]})}function Mg({toc:e,runtime:t,onSourceSelect:n,libraryAvailable:r,libraryOpen:o,setLibraryOpen:i,scale:l,setScale:s,nvimVisible:u,setNvimVisible:d,nvimSyncMode:m,setNvimSyncMode:y,nvimControlMode:g,setNvimControlMode:f,nvimExpanded:S,setNvimExpanded:b,nvimFit:_,setNvimFit:p,allocatorVisible:c,setAllocatorVisible:h,hasAllocatorModel:w,diagramSpread:C,setDiagramSpread:A,hasDiagramSpread:P,nvimSummary:z,progress:U,progressError:F}){return(0,a.jsx)("nav",{className:"topbar",children:(0,a.jsxs)("div",{className:"topbar-inner",children:[r&&(0,a.jsxs)("button",{type:"button",className:`card-library-toggle${o?" is-active":""}`,"aria-controls":"side-panel-left-library","aria-expanded":o,"aria-label":o?"Alt+0: zamknij serie i karty":"Alt+0: poka\u017C serie i karty",title:o?"Alt+0 \xB7 Zamknij serie i karty":"Alt+0 \xB7 Serie i karty",onClick:()=>i(!o),children:[(0,a.jsx)("span",{"aria-hidden":"true",className:"card-library-toggle-icon",children:"\u2630"}),(0,a.jsx)("kbd",{children:"A0"}),(0,a.jsx)("span",{children:"KARTY"})]}),(0,a.jsx)(Rg,{runtime:t,onSelect:n}),(0,a.jsxs)("details",{children:[(0,a.jsx)("summary",{children:"Spis tre\u015Bci"}),(0,a.jsx)("div",{className:"toc-links",children:e.map(ae=>(0,a.jsx)("a",{href:`#${ae.id}`,children:ae.label},ae.id))})]}),(0,a.jsxs)("div",{className:"topbar-nvim-section","aria-label":"Panel Neovima",children:[(0,a.jsx)("span",{className:"nvim-toolbar-boundary","aria-hidden":"true",children:"|"}),(0,a.jsx)("span",{className:"nvim-toolbar-context",children:"NVIM"}),(0,a.jsxs)("div",{className:"nvim-toolbar-actions","aria-label":"Narz\u0119dzia okna Neovima",children:[(0,a.jsxs)("button",{className:"viewer-nvim-toggle nvim-toolbar-button","aria-label":u?"Alt+1: ukryj Neovim":"Alt+1: poka\u017C Neovim",title:u?"Alt+1 \xB7 Ukryj Neovim":"Alt+1 \xB7 Poka\u017C Neovim","aria-pressed":u,onClick:()=>d(!u),children:[(0,a.jsx)("kbd",{className:"nvim-toolbar-key",children:"A1"}),(0,a.jsx)("span",{className:"nvim-toolbar-label",children:"PANEL"})]}),(0,a.jsxs)("button",{type:"button",className:`nvim-control-indicator nvim-toolbar-button${z.syncMode?" is-active":""}${z.syncReady?" is-engaged":""}`,"aria-label":`Alt+2: synchronizacja ${m?"w\u0142\u0105czona":"wy\u0142\u0105czona"}`,title:`Alt+2 \xB7 SYNC ${m?"ON":"OFF"}`,"aria-pressed":m,onClick:()=>y(!m),children:[(0,a.jsx)("kbd",{className:"nvim-toolbar-key",children:"A2"}),(0,a.jsx)("span",{className:"nvim-toolbar-label",children:"SYNC"})]}),(0,a.jsxs)("button",{type:"button",className:`nvim-work-indicator nvim-toolbar-button${g?" is-active":""}${z.controlEnabled?" is-engaged":""}`,"aria-label":`Alt+3: sterowanie ${m?g?"w\u0142\u0105czone":"wy\u0142\u0105czone":"niedost\u0119pne"}`,title:m?`Alt+3 \xB7 Sterowanie ${g?"ON":"OFF"}`:"Alt+3 \xB7 Sterowanie wymaga SYNC ON","aria-pressed":g,disabled:!m,onClick:()=>f(!g),children:[(0,a.jsx)("kbd",{className:"nvim-toolbar-key",children:"A3"}),(0,a.jsx)("span",{className:"nvim-toolbar-label",children:"CTRL"})]}),(0,a.jsxs)("button",{type:"button",className:`nvim-height-toggle nvim-toolbar-button${S?" is-active":""}`,"aria-label":`Alt+4: ${S?"przywr\xF3\u0107 normaln\u0105 wysoko\u015B\u0107":"ustaw wysoki panel"}`,title:S?"Alt+4 \xB7 Normalna wysoko\u015B\u0107":"Alt+4 \xB7 Wysoki panel 90%","aria-pressed":S,onClick:()=>b(!S),children:[(0,a.jsx)("kbd",{className:"nvim-toolbar-key",children:"A4"}),(0,a.jsx)("span",{className:"nvim-toolbar-label",children:"HIGH"})]}),(0,a.jsxs)("button",{type:"button",className:`nvim-height-toggle nvim-toolbar-button${_?" is-active":""}`,"aria-label":`Alt+5: automatyczne dopasowanie ${_?"w\u0142\u0105czone":"wy\u0142\u0105czone"}`,title:_?"Alt+5 \xB7 Wy\u0142\u0105cz auto-fit":"Alt+5 \xB7 Poka\u017C ca\u0142y Neovim","aria-pressed":_,onClick:()=>p(!_),children:[(0,a.jsx)("kbd",{className:"nvim-toolbar-key",children:"A5"}),(0,a.jsx)("span",{className:"nvim-toolbar-label",children:"FIT"})]}),w&&(0,a.jsxs)("button",{type:"button",className:`nvim-height-toggle nvim-toolbar-button${c?" is-active":""}`,"aria-label":`Alt+6: model alokatora ${c?"przypi\u0119ty":"ukryty"}`,title:c?"Alt+6 \xB7 Ukryj model alokatora":"Alt+6 \xB7 Przypnij model alokatora","aria-pressed":c,onClick:()=>h(!c),children:[(0,a.jsx)("kbd",{className:"nvim-toolbar-key",children:"A6"}),(0,a.jsx)("span",{className:"nvim-toolbar-label",children:"MODEL"})]}),P&&(0,a.jsxs)("button",{type:"button",className:`nvim-height-toggle nvim-toolbar-button${C?" is-active":""}`,"aria-label":`Alt+7: diagram UML ${C?"jako osobne kartki A4":"jako po\u0142\u0105czony spread"}`,title:C?"Alt+7 \xB7 Osobne kartki A4":"Alt+7 \xB7 Po\u0142\u0105cz strony UML","aria-pressed":C,onClick:()=>A(!C),children:[(0,a.jsx)("kbd",{className:"nvim-toolbar-key",children:"A7"}),(0,a.jsx)("span",{className:"nvim-toolbar-label",children:"UML"})]})]}),(0,a.jsx)("span",{className:"nvim-toolbar-boundary","aria-hidden":"true",children:"|"}),u&&(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("span",{className:`nvim-status nvim-status--${z.state}`,children:_p[z.state]}),(0,a.jsxs)("span",{className:"topbar-nvim-meta",title:z.message,children:[(0,a.jsx)("strong",{children:z.instance??"Neovim MCP"}),z.detail&&(0,a.jsx)("small",{children:z.detail})]}),(0,a.jsxs)("span",{className:"nvim-shortcut-strip","aria-label":"Skr\xF3ty panelu Neovima",children:[(0,a.jsx)("kbd",{children:"F1"})," reset \xB7 ",(0,a.jsx)("kbd",{children:"F2"})," sync \xB7 ",(0,a.jsx)("kbd",{children:"Alt+W"})," okna \xB7 ",(0,a.jsx)("kbd",{children:"F12"})," skr\xF3ty"]})]})]}),(0,a.jsx)(ah,{progress:U,error:F}),(0,a.jsxs)("div",{className:"viewer-zoom-controls",children:[(0,a.jsxs)("output",{className:"viewer-zoom-status",children:["P\u0142\xF3tno ",Math.round(l*100),"% \xB7 Tre\u015B\u0107 100%"]}),(0,a.jsx)("button",{className:"viewer-zoom-reset",onClick:()=>s(2.18),children:"Reset zoom"})]})]})})}function fp(e,t){for(let n of e.sections)for(let r of n.assets){let o=r.interactive;if(!(o?.kind!=="uml-sequence"||!o.phases?.length)&&!(t?.task.id&&o.task?.id!==t.task.id)&&!(t?.block.id&&o.block?.id!==t.block.id))for(let i of o.phases){if(t?.phase.id&&i.id!==t.phase.id)continue;let l=t?.step.id?i.steps.find(s=>s.id===t.step.id):i.steps[0];if(l)return{task:o.task??null,block:o.block??null,phase:{id:i.id,label:i.label},step:l,focusLevel:t?.focus_level??"step",activate:!1}}}return null}function Ig({visible:e,model:t}){let[n,r]=(0,k.useState)(null);if((0,k.useEffect)(()=>{let g=f=>{r(f.detail??null)};return window.addEventListener("stem:nvim-step",g),r(fp(t)),fetch("/api/navigation/current",{cache:"no-store"}).then(f=>f.ok?f.json():Promise.reject()).then(f=>{let S=fp(t,f.current??null);S&&r(S)}).catch(()=>{}),()=>window.removeEventListener("stem:nvim-step",g)},[t]),!e)return null;let o=n?.focusLevel??"card",i=n?.step.snapshot?.memory.arena,l=!!(n&&o!=="card"&&o!=="task"),s=Math.max(1,Number(i?.size_bytes??64)),u=i?.offset==null?null:Math.max(0,Math.min(s,Number(i.offset))),d=i?.bytes.trim().split(/\s+/).filter(Boolean)??[],m=Math.min(s,64),y=n?[...(n.step.snapshot?.memory.items??[]).map(g=>({name:g.name,value:g.value})),...(n.step.snapshot?.frame.fields??[]).map(g=>({name:g.name,value:g.value}))].filter((g,f,S)=>S.findIndex(b=>b.name===g.name)===f).slice(0,8):[];return(0,a.jsxs)("section",{className:"allocator-panel","aria-label":"Przypi\u0119ty model alokatora",children:[(0,a.jsxs)("header",{children:[(0,a.jsx)("span",{children:"ALLOCATOR \xB7 PINNED"}),(0,a.jsx)("strong",{children:n?`${n.phase.label} \xB7 ${String(n.step.number).padStart(2,"0")} ${n.step.label}`:"Oczekiwanie na diagram UML"}),(0,a.jsx)("small",{children:l?n.step.snapshot?.description??n.step.description:"Wybierz co najmniej BLOCK. F2 wybierze jego pierwszy krok i wykona synchronizacj\u0119."})]}),(0,a.jsxs)("div",{className:"allocator-panel-body",children:[(0,a.jsxs)("dl",{className:"allocator-metrics",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)("dt",{children:"FOCUS"}),(0,a.jsx)("dd",{children:o.toUpperCase()})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)("dt",{children:"BASE"}),(0,a.jsx)("dd",{children:i?.base??"\u2014"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)("dt",{children:"ALLOCP"}),(0,a.jsx)("dd",{children:i?.cursor??"\u2014"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)("dt",{children:"USED / FREE"}),(0,a.jsx)("dd",{children:u==null?"\u2014":`${u} / ${s-u} B`})]})]}),(0,a.jsxs)("div",{className:`allocator-arena-view${l?"":" is-inactive"}`,children:[(0,a.jsxs)("div",{className:"allocator-arena-labels",children:[(0,a.jsx)("span",{children:"allocbuf[0]"}),(0,a.jsx)("strong",{children:u==null?"allocp jeszcze nieustawiony":`offset ${u}`}),(0,a.jsxs)("span",{children:["allocbuf[",s-1,"]"]})]}),(0,a.jsxs)("div",{className:"allocator-pinned-arena",style:{"--allocator-size":m},children:[Array.from({length:m},(g,f)=>(0,a.jsx)("i",{className:u!=null&&f(0,a.jsxs)("div",{children:[(0,a.jsx)("span",{children:g.name}),(0,a.jsx)("code",{children:g.value})]},g.name)):(0,a.jsx)("p",{children:"Brak snapshotu."})})]})]})}var Yi=()=>({text:" ",highlight:0});function gp(e=1,t=1){return{width:e,height:t,cells:Array.from({length:t},()=>Array.from({length:e},Yi)),highlights:new Map,foreground:15263976,background:1053461,cursor:{row:0,column:0}}}function Og(e,t,n){if(t<=0||n<=0)return;let r=e.cells;e.width=t,e.height=n,e.cells=Array.from({length:n},(o,i)=>Array.from({length:t},(l,s)=>r[i]?.[s]??Yi()))}function Tp(e){return`stem-card:nvim-grid:${e}`}function $g(e,t){if(t.width<=1||t.height<=1)return;let n={schema:"stem-nvim-grid.v1",snapshot_ref:e,saved_at:new Date().toISOString(),width:t.width,height:t.height,cells:t.cells.map(r=>r.map(o=>[o.text,o.highlight])),highlights:Array.from(t.highlights.entries()),foreground:t.foreground,background:t.background,cursor:{...t.cursor}};try{localStorage.setItem(Tp(e),JSON.stringify(n))}catch{}}function Rp(e,t){try{return e?.schema!=="stem-nvim-grid.v1"||e.snapshot_ref!==t||!Number.isInteger(e.width)||!Number.isInteger(e.height)||e.width<2||e.height<2||e.width>400||e.height>200||e.cells.length!==e.height?null:{width:e.width,height:e.height,cells:e.cells.map(n=>n.map(([r,o])=>({text:typeof r=="string"?r:" ",highlight:Number.isInteger(o)?o:0}))),highlights:new Map(e.highlights),foreground:e.foreground,background:e.background,cursor:e.cursor}}catch{return null}}function Fg(e){try{return Rp(JSON.parse(localStorage.getItem(Tp(e))??"null"),e)}catch{return null}}async function hp(e){let t=e.snapshot_ref;if(!t)return null;let n=Fg(t);if(n)return n;let r=e.view?.recorded_grid_ref;if(!r||/^(?:[a-z]+:|\/\/)/i.test(r))return null;try{let o=await fetch(r,{cache:"no-store"});return o.ok?Rp(await o.json(),t):null}catch{return null}}var Dg=[1842207,12590120,3064446,16106001,1997028,9978299,702940,12631996,6184036,15545147,5759881,16311388,5349887,12607947,5231357,16184820];function ho(e){let t=Number(e);if(!Number.isInteger(t)||t<0||t>255)return;if(t<16)return Dg[t];if(t<232){let r=t-16,o=[0,95,135,175,215,255],i=o[Math.floor(r/36)],l=o[Math.floor(r%36/6)],s=o[r%6];return i<<16|l<<8|s}let n=8+(t-232)*10;return n<<16|n<<8|n}function jg(e){if(!e||typeof e!="object")return null;let t=e;return Object.keys(t).length?{...t,foreground:ho(t.foreground),background:ho(t.background),special:ho(t.special)}:null}function Ug(e){if(!e||typeof e!="object")return null;let t=e;return Object.keys(t).length?{...t}:null}function Vg(e,t){for(let n of t){if(!Array.isArray(n)||typeof n[0]!="string")continue;let r=n[0],o=n.slice(1);for(let i of o)if(Array.isArray(i)){if(r==="grid_resize"){let[l,s,u]=i.map(Number);l===1&&Og(e,s,u)}else if(r==="default_colors_set"){let l=Number(i[0]),s=Number(i[1]),u=l>=0?l:ho(i[3]),d=s>=0?s:ho(i[4]);typeof u=="number"&&Number.isFinite(u)&&u>=0&&(e.foreground=u),typeof d=="number"&&Number.isFinite(d)&&d>=0&&(e.background=d)}else if(r==="hl_attr_define"){let l=Number(i[0]),s=jg(i[2])??Ug(i[1]);Number.isInteger(l)&&s&&typeof s=="object"&&e.highlights.set(l,s)}else if(r==="grid_clear"&&Number(i[0])===1)e.cells=Array.from({length:e.height},()=>Array.from({length:e.width},Yi));else if(r==="grid_cursor_goto"&&Number(i[0])===1)e.cursor={row:Number(i[1]),column:Number(i[2])};else if(r==="grid_line"&&Number(i[0])===1){let l=Number(i[1]),s=Number(i[2]),u=i[3];if(!e.cells[l]||!Array.isArray(u))continue;let d=0;for(let m of u){if(!Array.isArray(m))continue;let y=typeof m[0]=="string"?m[0]:" ";Number.isInteger(m[1])&&(d=Number(m[1]));let g=Number.isInteger(m[2])?Number(m[2]):1;for(let f=0;f=0&&(e.cells[l][s]={text:y,highlight:d}),s+=1}}else if(r==="grid_scroll"&&Number(i[0])===1){let l=Number(i[1]),s=Number(i[2]),u=Number(i[3]),d=Number(i[4]),m=Number(i[5]),y=Number(i[6]),g=e.cells.map(f=>f.map(S=>({...S})));for(let f=l;f=l&&b=u&&_i.text||" ").join(""),r=[],o=0;for(;o=n&&t.cursor.rowz.column-P.column||P.row-z.row):["asm","reg","memory"].includes(C??"")&&w.sort((P,z)=>P.column-z.column||P.row-z.row);let A=w[Math.max(0,Number(d.occurrence??1)-1)];m=A?.row??-1,y=A?.column??-1,f=d.text.length}if(m<0||y<0||m>=t.height||y>=t.width)continue;let S=Math.max(0,Number(d.padding_cells??1)),b=Math.max(0,y-S)*i,_=Math.max(0,m-S)*l,p=Math.min(t.width-y+S,f+S*2)*i,c=Math.min(t.height-m+S,g+S*2)*l,h=s[u.tone??"danger"];if(o.strokeStyle=h,o.lineWidth=2,u.shape==="underline"?(o.beginPath(),o.moveTo(b,_+c-2),o.lineTo(b+p,_+c-2),o.stroke()):o.strokeRect(b+1,_+1,Math.max(1,p-2),Math.max(1,c-2)),u.label){o.font="700 10px system-ui, sans-serif";let w=o.measureText(u.label).width+8;o.fillStyle=h,o.fillRect(b+1,Math.max(0,_-15),w,14),o.fillStyle="#fff",o.fillText(u.label,b+5,Math.max(10,_-4))}}}function Wg(e){if(e.nativeEvent.isComposing||e.metaKey)return null;let n={Enter:"CR",Escape:"Esc",Backspace:"BS",Delete:"Del",Tab:"Tab",ArrowLeft:"Left",ArrowRight:"Right",ArrowUp:"Up",ArrowDown:"Down",Home:"Home",End:"End",PageUp:"PageUp",PageDown:"PageDown",Insert:"Insert"}[e.key]??(/^F\d{1,2}$/.test(e.key)?e.key:null),r=[e.ctrlKey?"C":"",e.altKey?"M":"",e.shiftKey&&n?"S":""].filter(Boolean).join("-");return n?`<${r?`${r}-`:""}${n}>`:e.key.length!==1?null:e.ctrlKey||e.altKey?`<${r}-${e.key==="<"?"lt":e.key}>`:e.key==="<"?"":e.key}function Gg({visible:e,syncMode:t,controlMode:n,expanded:r,fit:o,scale:i,onSummaryChange:l}){let s=(0,k.useRef)(null),u=(0,k.useRef)(null),d=(0,k.useRef)(null),m=(0,k.useRef)(0),y=(0,k.useRef)(0),g=(0,k.useRef)(0),f=(0,k.useRef)(gp()),S=(0,k.useRef)(null),b=(0,k.useRef)(null),_=(0,k.useRef)(null),p=(0,k.useRef)(0),c=(0,k.useRef)(null),h=(0,k.useRef)(null),[w,C]=(0,k.useState)(null),[A,P]=(0,k.useState)(!1),[z,U]=(0,k.useState)(0),[F,ae]=(0,k.useState)(!1),[me,q]=(0,k.useState)(()=>{let T=Number(localStorage.getItem("stem-card-nvim-height-vh"));return Number.isFinite(T)&&T>=25&&T<=90?T:75}),[V,Re]=(0,k.useState)({state:"hidden"}),[Se,J]=(0,k.useState)({state:"idle"}),at=Se.state==="replaying"||Se.state==="verifying",bt=t&&V.state==="connected"&&!at,Me=n&&F&&V.state==="connected"&&!at,ge=()=>{s.current&&Kg(s.current,f.current,_.current===b.current?.step.snapshot_ref?b.current?.step.view?.annotations??[]:[])},yr=()=>{m.current||(m.current=window.requestAnimationFrame(()=>{m.current=0,ge()}))};if((0,k.useEffect)(()=>{let T=D=>{let G=D.detail;b.current=G,C(G),_.current=null;let te=G.step.snapshot_ref;if(V.state!=="connected"&&te&&hp(G.step).then(Q=>{!Q||b.current?.step.snapshot_ref!==te||(f.current=Q,P(!0),_.current=te,J({state:"idle",snapshot_ref:te,message:"Pokazano ostatni zapisany, zweryfikowany ekran."}),ge())}),ge(),!G.activate)return;let st=G.step.mode??"RUN",xt=async()=>{if(!G.step.code_ref)return null;let Q=await fetch("/api/nvim/open",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({code_ref:G.step.code_ref,actor:document.documentElement.dataset.cardClientId??"browser"})}),Ae=await Q.json().catch(()=>({}));if(!Q.ok)throw new Error(Ae.message??Ae.error??`HTTP ${Q.status}`);return Ae};if(st==="CODE"||!te){xt().then(()=>{J({state:"ready",phase:"code",message:G.step.code_ref?`Otwarto ${G.step.code_ref}; program nie zosta\u0142 zrestartowany.`:"Krok CODE nie ma przypisanego code_ref."})}).catch(Q=>{J({state:"failed",phase:"code",message:Q instanceof Error?Q.message:String(Q)})});return}if(!e||V.state!=="connected"){J({state:"failed",snapshot_ref:te,message:"Cel jest offline; pozostaje zapisany wynik tego kroku."});return}let ze=xt(),oe=Math.max(p.current+1,Date.now());p.current=oe,c.current?.abort();let wt=new AbortController;c.current=wt,J({state:"replaying",phase:"dispatch",generation:oe,snapshot_ref:te,message:"Resetuj\u0119 cel i odtwarzam wykonanie do wybranego kroku."});let kn=0,Dt=async()=>{try{let Q=await fetch("/api/checkpoint/status",{cache:"no-store",signal:wt.signal});if(!Q.ok)return;let Ae=await Q.json(),ut=Ae.active?.generation===oe?Ae.active:null;if(!ut||p.current!==oe)return;J({state:ut.phase==="verify"?"verifying":"replaying",phase:ut.phase,generation:oe,snapshot_ref:te,message:ut.message})}catch{}};kn=window.setInterval(Dt,120),Dt(),ze.then(()=>fetch("/api/checkpoint/activate",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({snapshot_ref:te,generation:oe}),signal:wt.signal})).then(async Q=>{let Ae=await Q.json();if(p.current!==oe)return;if(!Q.ok||Ae.status!=="ready")throw new Error(Ae.message??`Replay zako\u0144czy\u0142 si\u0119 kodem HTTP ${Q.status}.`);let ut=d.current;if(ut?.readyState!==WebSocket.OPEN)throw new Error("Replay zako\u0144czony, ale most Neovima jest offline.");let Ne=y.current;ut.send(JSON.stringify({type:"refresh"}));let Ee=Date.now()+1800;for(;y.current<=Ne&&Date.now()window.setTimeout(kt,20));if(y.current<=Ne)throw new Error("Neovim nie potwierdzi\u0142 pe\u0142nego redraw po replay.");if(p.current!==oe)return;_.current=te,J({state:"ready",phase:Ae.phase,generation:oe,snapshot_ref:te,message:Ae.message??"Stan maszyny zosta\u0142 odtworzony i zweryfikowany."}),$g(te,f.current),ge(),await new Promise(kt=>window.requestAnimationFrame(()=>kt()));let vr=s.current;if(vr&&b.current?.step.snapshot_ref===te)try{fetch("/api/evidence",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({actor:document.documentElement.dataset.cardClientId??"browser",snapshot_ref:te,event_id:G.step.event_id??null,strategy_ref:G.step.strategy_ref??null,code_ref:G.step.code_ref??null,caption:`${String(G.step.number).padStart(2,"0")} ${G.step.label}`,data_url:vr.toDataURL("image/png")})}).catch(()=>{})}catch{}}).catch(Q=>{wt.signal.aborted||p.current!==oe||(hp(G.step).then(Ae=>{!Ae||b.current?.step.snapshot_ref!==te||(f.current=Ae,P(!0),_.current=te,ge())}),J({state:"failed",generation:oe,snapshot_ref:te,message:Q instanceof Error?Q.message:String(Q)}))}).finally(()=>{window.clearInterval(kn),c.current===wt&&(c.current=null)})};return window.addEventListener("stem:nvim-step",T),()=>{window.removeEventListener("stem:nvim-step",T),c.current?.abort()}},[V.state,e]),(0,k.useEffect)(()=>{localStorage.setItem("stem-card-nvim-height-vh",me.toFixed(2))},[me]),(0,k.useEffect)(()=>{let T=window.requestAnimationFrame(ge);return()=>window.cancelAnimationFrame(T)},[r,o,i]),(0,k.useEffect)(()=>{let T=()=>{let D=d.current;D?.readyState===WebSocket.OPEN&&D.send(JSON.stringify({type:"refresh"})),ge()};return window.addEventListener("stem:nvim-refresh",T),()=>window.removeEventListener("stem:nvim-refresh",T)},[]),(0,k.useEffect)(()=>{let T=V.state;V.state==="connected"&&(T=Se.state==="idle"?"connected":Se.state),l({state:T,message:Se.message??V.message,instance:V.target?.instance??V.target?.container_name,detail:[V.target?.profile??w?.step.view?.connection_ref,w?`${String(w.step.number).padStart(2,"0")} ${w.step.label}`:void 0,Se.phase].filter(Boolean).join(" \xB7 "),syncMode:t,syncReady:bt,controlMode:n,controlEnabled:Me,screenCursor:{...f.current.cursor},grid:{columns:f.current.width,rows:f.current.height}})},[w,Se,Me,n,z,l,V,t,bt]),(0,k.useEffect)(()=>()=>window.clearTimeout(g.current),[]),(0,k.useEffect)(()=>{if(!Me)return;let T=D=>{let G=d.current;if(G?.readyState!==WebSocket.OPEN)return;if(D.altKey&&!D.ctrlKey&&!D.metaKey&&D.key.toLowerCase()==="w"){D.preventDefault(),D.stopImmediatePropagation(),D.repeat||G.send(JSON.stringify({type:"input",keys:""}));return}let te={ArrowLeft:"h",ArrowDown:"j",ArrowUp:"k",ArrowRight:"l"};D.altKey&&!D.ctrlKey&&!D.metaKey&&te[D.key]&&(D.preventDefault(),D.stopImmediatePropagation(),D.repeat||G.send(JSON.stringify({type:"input",keys:`${te[D.key]}`})))};return window.addEventListener("keydown",T,!0),()=>window.removeEventListener("keydown",T,!0)},[Me]),(0,k.useEffect)(()=>{if(!e||!t){d.current?.close(),d.current=null,ae(!1),Re(e?{state:"offline",message:"SYNC OFF: pokazuj\u0119 zapisany ekran bez po\u0142\u0105czenia z kontenerem."}:{state:"hidden"});return}let T=!1,D=0,G=()=>{if(T)return;let te=window.location.protocol==="https:"?"wss:":"ws:",st=new WebSocket(`${te}//${window.location.host}/api/nvim-ui`);d.current=st,Re({state:"connecting"}),st.addEventListener("message",xt=>{try{let ze=JSON.parse(String(xt.data));if(ze.type==="status"){typeof ze.epoch=="string"&&S.current&&S.current!==ze.epoch&&(f.current=gp(),P(!1),_.current=null),typeof ze.epoch=="string"&&(S.current=ze.epoch),Re(ze);return}ze.type==="redraw"&&Array.isArray(ze.events)&&(Vg(f.current,ze.events),f.current.width>1&&f.current.height>1&&P(!0),ze.events.some(oe=>Array.isArray(oe)&&oe[0]==="flush")&&(y.current+=1,yr(),g.current||(g.current=window.setTimeout(()=>{g.current=0,U(oe=>oe+1)},120))))}catch{}}),st.addEventListener("close",()=>{d.current===st&&(d.current=null),T||(D=window.setTimeout(G,1200))}),st.addEventListener("error",()=>{Re(xt=>({...xt,state:"offline",message:"Most Neovima jest chwilowo niedost\u0119pny."}))})};return G(),()=>{T=!0,window.clearTimeout(D),window.cancelAnimationFrame(m.current),m.current=0,d.current?.close(),d.current=null}},[t,e]),!e)return null;let se=T=>{let D=d.current;D?.readyState===WebSocket.OPEN&&D.send(JSON.stringify(T))},Zt=T=>{let D=T.currentTarget.getBoundingClientRect();return{row:Math.max(0,Math.min(f.current.height-1,Math.floor((T.clientY-D.top)/D.height*f.current.height))),column:Math.max(0,Math.min(f.current.width-1,Math.floor((T.clientX-D.left)/D.width*f.current.width)))}},ue=T=>[T.shiftKey?"S":"",T.ctrlKey?"C":"",T.altKey?"A":""].filter(Boolean).join("-");return(0,a.jsxs)("section",{className:"nvim-panel","aria-label":"Neovim po\u0142\u0105czony z kontenerem",onWheel:T=>{T.stopPropagation();let D=T.target;(!(D instanceof Element)||!D.closest(".nvim-screen-scroll"))&&T.preventDefault()},children:[(0,a.jsxs)("div",{ref:u,className:"nvim-screen-scroll",style:o?void 0:{height:`${r?90:me}vh`},"data-sync":t?"on":"off","data-control":Me?"true":"false",tabIndex:0,onPointerEnter:T=>{ae(!0),T.currentTarget.focus({preventScroll:!0})},onPointerLeave:T=>{ae(!1),T.currentTarget.blur()},onMouseDown:T=>T.currentTarget.focus({preventScroll:!0}),onKeyDown:T=>{if(!Me)return;let D=Wg(T);D&&(T.preventDefault(),T.stopPropagation(),se({type:"input",keys:D}))},children:[(0,a.jsx)("div",{className:"nvim-screen-stage",style:o?void 0:{height:`${(r?90:me)/Math.max(i,.25)}vh`},children:(0,a.jsx)("div",{className:"nvim-screen-surface",children:(0,a.jsx)("div",{className:"nvim-screen-frame",children:(0,a.jsx)("div",{className:"nvim-screen-content",children:(0,a.jsx)("div",{className:"nvim-screen-grid",children:(0,a.jsx)("canvas",{ref:s,className:"nvim-screen",tabIndex:-1,"aria-label":Me?"Interaktywny ekran Neovima":"Ekran Neovima",onMouseDown:T=>{if(!Me||V.state!=="connected")return;T.preventDefault();let D=Zt(T),G=T.button===1?"middle":T.button===2?"right":"left";se({type:"mouse",button:G,action:"press",modifier:ue(T),...D})},onMouseUp:T=>{if(!Me||V.state!=="connected")return;T.preventDefault();let D=Zt(T),G=T.button===1?"middle":T.button===2?"right":"left";se({type:"mouse",button:G,action:"release",modifier:ue(T),...D})},onWheel:T=>{if(!Me||V.state!=="connected")return;T.preventDefault();let D=Zt(T);se({type:"mouse",button:"wheel",action:T.deltaY<0?"up":"down",modifier:ue(T),...D})},onContextMenu:T=>Me&&T.preventDefault()})})})})})}),!A&&(0,a.jsxs)("div",{className:"nvim-screen-empty",children:[(0,a.jsx)("strong",{children:_p[V.state]}),(0,a.jsx)("span",{children:V.message??"Oczekiwanie na ekran Neovima lub zapisany stan kroku."})]})]}),(0,a.jsx)("div",{className:"nvim-resize-handle",role:"separator","aria-label":"Zmie\u0144 wysoko\u015B\u0107 panelu Neovima","aria-orientation":"horizontal","aria-valuemin":25,"aria-valuemax":90,"aria-valuenow":Math.round(me),tabIndex:0,title:"Przeci\u0105gnij, aby zmieni\u0107 wysoko\u015B\u0107. Dwuklik: 75%.",onPointerDown:T=>{T.preventDefault(),h.current={y:T.clientY,heightVh:me},T.currentTarget.setPointerCapture(T.pointerId)},onPointerMove:T=>{let D=h.current;if(!D)return;let G=(T.clientY-D.y)/window.innerHeight*100;q(Math.max(25,Math.min(90,D.heightVh+G)))},onPointerUp:T=>{h.current=null,T.currentTarget.hasPointerCapture(T.pointerId)&&T.currentTarget.releasePointerCapture(T.pointerId)},onPointerCancel:()=>{h.current=null},onDoubleClick:()=>q(75),onKeyDown:T=>{if(T.key==="ArrowUp"||T.key==="ArrowDown"){T.preventDefault();let D=T.key==="ArrowUp"?-2:2;q(G=>Math.max(25,Math.min(90,G+D)))}else T.key==="Home"&&(T.preventDefault(),q(75))}})]})}function Yg({column:e,row:t}){return e==="chapter"||e==="task"||e==="version"?(0,a.jsx)("td",{children:(0,a.jsx)("code",{children:t[e]})}):e==="idea"?(0,a.jsx)("td",{dangerouslySetInnerHTML:{__html:t.idea_html}}):e==="status"?(0,a.jsx)("td",{children:(0,a.jsx)("span",{className:"scope-status","data-status":t.status,title:t.status,children:t.status_label})}):(0,a.jsx)("td",{children:t[e]})}function Qg({front:e}){let t=e.scope_columns??["chapter","task","idea","priority"];return(0,a.jsxs)(bs,{header:e.header_html,footer:e.footer_html,left:e.left_margin_html,right:e.right_margin_html,className:"front-sheet",children:[(0,a.jsxs)("section",{className:"front-scope front-goal",children:[(0,a.jsx)("h2",{children:e.goal_title}),(0,a.jsx)(Ft,{html:e.goal_html})]}),(0,a.jsxs)("section",{className:"front-scope front-card-scope",children:[(0,a.jsx)("h2",{children:e.scope_title}),e.scope_html&&(0,a.jsx)(Ft,{html:e.scope_html}),(0,a.jsx)("div",{className:"scope-table-wrap",children:(0,a.jsxs)("table",{className:"scope-table scope-table--task",children:[(0,a.jsx)("thead",{children:(0,a.jsx)("tr",{children:e.scope_headers.map(n=>(0,a.jsx)("th",{children:n},n))})}),(0,a.jsx)("tbody",{children:e.scope_rows.map(n=>(0,a.jsx)("tr",{className:n.key?"scope-row--key":"",children:t.map(r=>(0,a.jsx)(Yg,{column:r,row:n},r))},`${n.chapter}-${n.task}`))})]})})]})]})}function qg({taskRef:e,step:t}){return(0,a.jsxs)("li",{id:`${e}-${t.id}`,children:[(0,a.jsx)("strong",{children:t.title}),(0,a.jsx)(Ft,{html:t.content_html})]})}function Xg({taskRef:e,block:t}){let n=t.kind==="exercise"?"\u0106wiczenie":"Block";return(0,a.jsxs)("section",{className:`task-block task-block--${t.kind}`,id:`${e}-${t.id}`,"data-stem-nav":"block",children:[(0,a.jsxs)("header",{children:[(0,a.jsx)("span",{children:n}),(0,a.jsx)("h4",{children:t.title})]}),(0,a.jsx)(Ft,{html:t.content_html}),t.steps.length>0&&(0,a.jsx)("ol",{className:"block-steps",children:t.steps.map(r=>(0,a.jsx)(qg,{taskRef:e,step:r},r.id))})]})}function Zg({task:e}){return e.flow.length?(0,a.jsxs)("article",{className:"task-flow",id:e.ref,"data-stem-nav":"task",children:[(0,a.jsxs)("header",{className:"task-flow-header",children:[(0,a.jsx)("span",{children:e.ref.toUpperCase()}),(0,a.jsx)("h3",{children:e.title}),e.uuid&&(0,a.jsx)("code",{children:e.uuid})]}),e.flow.map(t=>(0,a.jsx)(Xg,{taskRef:e.ref,block:t},t.id)),(0,a.jsxs)("footer",{children:[(0,a.jsx)("strong",{children:"Task acceptance:"})," ",e.criterion]})]}):(0,a.jsxs)("article",{className:"task-legacy",id:e.ref,"data-stem-nav":"task",children:[(0,a.jsx)("strong",{children:e.ref.toUpperCase()}),(0,a.jsx)(Ft,{html:e.prompt_html??""}),(0,a.jsxs)("footer",{children:[(0,a.jsx)("strong",{children:"Task acceptance:"})," ",e.criterion]})]})}var yp={compile:"KOMPILACJA",bss:".BSS",stack:"STOS",registers:"REJESTRY",text:".TEXT"},vp=["#9bcfe2","#c4dfc0","#f4c889","#d2b6dc"];function Jg({config:e,title:t}){let n=e.arena_size??64,r=e.allocations??[5,7],o=e.symbols??[],i=`${e.storage_key??"allocator-memory-flow"}-stage`,[l,s]=(0,k.useState)(()=>Math.min(r.length,Number(localStorage.getItem(i)??0))),[u,d]=(0,k.useState)(o[0]?.id??"");(0,k.useEffect)(()=>localStorage.setItem(i,String(l)),[l,i]);let m=r.slice(0,l).reduce((_,p)=>_+p,0),y=o.find(_=>_.id===u)??o[0],g=Array.from({length:n},(_,p)=>p),f=_=>{let p=0;for(let c=0;co.some(p=>p.area===_));return(0,a.jsxs)("div",{className:"memory-react","aria-label":`Interaktywny diagram: ${t}`,children:[(0,a.jsxs)("header",{children:[(0,a.jsxs)("div",{children:[(0,a.jsx)("small",{children:"INTERAKTYWNY MODEL PAMI\u0118CI"}),(0,a.jsx)("h3",{children:t})]}),(0,a.jsxs)("div",{className:"memory-actions",children:[(0,a.jsx)("button",{"aria-pressed":l===0,onClick:()=>s(0),children:"definicje / reset"}),r.map((_,p)=>(0,a.jsxs)("button",{"aria-pressed":l===p+1,onClick:()=>s(p+1),children:["alloc(",_,")"]},`${_}-${p}`))]})]}),(0,a.jsx)("div",{className:"memory-lanes",children:b.map(_=>(0,a.jsxs)("section",{className:`memory-lane ${_}`,children:[(0,a.jsx)("b",{children:yp[_]}),o.filter(p=>p.area===_).map(p=>(0,a.jsx)("button",{className:u===p.id?"active":"",onClick:()=>d(p.id),children:(0,a.jsx)("code",{children:p.declaration})},p.id))]},_))}),(0,a.jsxs)("div",{className:"arena-wrap",children:[(0,a.jsxs)("div",{className:"arena-label",children:[(0,a.jsxs)("code",{children:["allocbuf[",n,"]"]}),(0,a.jsxs)("span",{children:["u\u017Cyte ",m," B / wolne ",n-m," B"]})]}),(0,a.jsxs)("div",{className:"arena",style:{"--allocp":m,"--arena-size":n},children:[g.map(_=>{let p=f(_);return(0,a.jsx)("i",{style:p>=0?{background:vp[p%vp.length]}:void 0},_)}),(0,a.jsxs)("span",{className:"allocp",children:["allocp \u2191 ",m]})]})]}),(0,a.jsxs)("p",{className:"memory-stage",children:[(0,a.jsxs)("strong",{children:[l+1,"/",r.length+1]})," ",(0,a.jsx)("code",{children:S})]}),y&&(0,a.jsxs)("aside",{className:"memory-inspector",children:[(0,a.jsx)("strong",{children:y.id}),(0,a.jsx)("code",{children:y.declaration}),(0,a.jsx)("span",{children:y.description})]})]})}function eh({config:e,asset:t,progress:n,onSetStatus:r}){let o=e.stages??[],i=`${e.storage_key??t.label}-uml-stage`,l=(0,k.useRef)(null),[s,u]=(0,k.useState)(""),[d,m]=(0,k.useState)(!1),[y,g]=(0,k.useState)(!1),[f,S]=(0,k.useState)(()=>Math.max(0,Math.min(o.length-1,Number(localStorage.getItem(i)??0))));(0,k.useEffect)(()=>localStorage.setItem(i,String(f)),[f,i]),(0,k.useEffect)(()=>{let c=!0;return m(!1),fetch(t.href,{cache:"no-store"}).then(h=>{if(!h.ok)throw new Error(`HTTP ${h.status}`);return h.text()}).then(h=>{let w=new DOMParser().parseFromString(h,"image/svg+xml"),C=w.documentElement;if(C.nodeName.toLowerCase()!=="svg"||w.querySelector("parsererror"))throw new Error("Niepoprawny SVG");w.querySelectorAll("script, foreignObject").forEach(A=>A.remove()),zp(C,t.tile),c&&u(C.outerHTML)}).catch(()=>{c&&m(!0)}),()=>{c=!1}},[t.href,t.tile]),(0,k.useEffect)(()=>{let c=l.current?.querySelector("svg");if(!c)return;c.setAttribute("role","img"),c.setAttribute("aria-label",t.alt),c.querySelectorAll("[data-uml-stage]").forEach(C=>{C.classList.remove("uml-stage-region","uml-stage-region-bg","uml-stage-region-outline","uml-stage-region-active","uml-stage-region-completed"),C.removeAttribute("data-uml-stage"),C.removeAttribute("data-uml-stage-label"),C.removeAttribute("role"),C.removeAttribute("tabindex"),C.removeAttribute("aria-label")});let h=Array.from(c.querySelectorAll("text")),w=Array.from(c.querySelectorAll("rect"));o.forEach((C,A)=>{if(!C.svg_label)return;let P=h.find(q=>(q.textContent??"").trim().startsWith(C.svg_label)),z=Number(P?.getAttribute("y"));if(!P||!Number.isFinite(z))return;let U=w.map(q=>{let V=Number(q.getAttribute("y")),Re=Number(q.getAttribute("height")),Se=Number(q.getAttribute("width")),J=q.getAttribute("style")??"",at=Number(J.match(/stroke-width:\s*([\d.]+)/)?.[1]??q.getAttribute("stroke-width")??0);return{rect:q,y:V,height:Re,width:Se,strokeWidth:at,area:Se*Re}}).filter(({y:q,height:V,width:Re,strokeWidth:Se})=>Number.isFinite(q)&&Number.isFinite(V)&&Number.isFinite(Re)&&Se>=2&&q<=z&&q+V>=z).sort((q,V)=>q.area-V.area),F=U[0];if(!F)return;let ae=U.filter(({y:q,height:V,width:Re})=>Math.abs(q-F.y)<.1&&Math.abs(V-F.height)<.1&&Math.abs(Re-F.width)<.1),me=!!(C.progress_id&&n?.items?.[C.progress_id]?.status==="approved");ae.forEach(({rect:q},V)=>{let Re=q.getAttribute("fill")!=="none";q.dataset.umlStage=String(A),q.dataset.umlStageLabel=C.label,q.classList.add("uml-stage-region",Re?"uml-stage-region-bg":"uml-stage-region-outline"),q.classList.toggle("uml-stage-region-active",A===f),q.classList.toggle("uml-stage-region-completed",me),V===0&&(q.setAttribute("role","button"),q.setAttribute("tabindex","0"),q.setAttribute("aria-label",`Poka\u017C etap ${C.label}`))})})},[t.alt,n,f,o,s]);let b=(c,h)=>{let w=l.current?.querySelector("svg");if(!w)return;let C=Array.from(w.querySelectorAll(".uml-stage-region-bg[data-uml-stage]")).filter(P=>{let z=P.getBoundingClientRect();return c>=z.left&&c<=z.right&&h>=z.top&&h<=z.bottom}).sort((P,z)=>{let U=P.getBoundingClientRect(),F=z.getBoundingClientRect();return U.width*U.height-F.width*F.height})[0],A=Number(C?.dataset.umlStage);Number.isInteger(A)&&S(A)},_=c=>{if(c.key!=="Enter"&&c.key!==" ")return;let h=c.target,w=Number(h.dataset?.umlStage);Number.isInteger(w)&&(c.preventDefault(),S(w))},p=o[f];return(0,a.jsxs)("div",{className:"uml-react",children:[(0,a.jsxs)("header",{children:[(0,a.jsxs)("div",{children:[(0,a.jsx)("small",{children:"KANONICZNY DIAGRAM UML \xB7 PLANTUML"}),(0,a.jsx)("h3",{children:"Reset i dwa przydzia\u0142y liniowe"})]}),(0,a.jsx)("div",{className:"uml-actions",children:o.map((c,h)=>(0,a.jsxs)("button",{"aria-pressed":f===h,"data-completed":c.progress_id&&n?.items?.[c.progress_id]?.status==="approved"?"true":"false",onClick:()=>S(h),children:[c.progress_id&&n?.items?.[c.progress_id]?.status==="approved"&&(0,a.jsx)("span",{"aria-hidden":"true",children:"\u2713 "}),c.label]},c.id))})]}),(0,a.jsxs)("div",{ref:l,className:"uml-canvas",onClick:c=>b(c.clientX,c.clientY),onKeyDown:_,children:[s?(0,a.jsx)("div",{className:`uml-svg-host${t.tile?" is-tiled":""}`,style:vs(t.tile),dangerouslySetInnerHTML:{__html:s}}):(0,a.jsx)("img",{src:t.href,alt:t.alt}),d&&(0,a.jsx)("span",{className:"uml-inline-warning",children:"Widok statyczny \u2014 nie uda\u0142o si\u0119 wczyta\u0107 interaktywnego SVG."})]}),p&&(0,a.jsxs)("aside",{className:"uml-stage",children:[(0,a.jsx)("strong",{children:p.label}),(0,a.jsx)("span",{children:p.description}),p.code_ref&&(0,a.jsx)("code",{children:p.code_ref})]}),p?.progress_id&&(0,a.jsxs)("footer",{className:"uml-approval",children:[(0,a.jsx)("span",{children:n?.items?.[p.progress_id]?.status==="approved"?"Ten stan jest zatwierdzony i zapisany w JSON-ie zaj\u0119\u0107.":"Kursor wybiera stan; zatwierdzenie zapisuje go w JSON-ie zaj\u0119\u0107."}),(0,a.jsx)("button",{disabled:!n||y,onClick:async()=>{if(!p.progress_id||!n)return;let c=n.items?.[p.progress_id]?.status==="approved";g(!0);try{await r(p.progress_id,c?"pending":"approved")}finally{g(!1)}},children:y?"Zapisywanie\u2026":n?.items?.[p.progress_id]?.status==="approved"?"Cofnij zatwierdzenie":"Zatwierd\u017A stan"})]})]})}function th({config:e,asset:t,progress:n,onSetStatus:r,continuation:o}){let i=e.phases??[],l=i.flatMap(v=>v.steps.map(x=>({phase:v,step:x}))),s=`${e.storage_key??t.label}-uml-step`,u=`${e.storage_key??t.label}-navigation-level`,d=(0,k.useRef)(null),m=(0,k.useRef)(null),y=(0,k.useRef)(new Map),g=(0,k.useRef)(null),f=(0,k.useRef)(0),S=(0,k.useRef)(!1),b=(0,k.useRef)(null),_=(0,k.useRef)(!0),[p,c]=(0,k.useState)(""),[h,w]=(0,k.useState)(!1),[C,A]=(0,k.useState)(!1),[P,z]=(0,k.useState)(null),[U,F]=(0,k.useState)(null),[ae,me]=(0,k.useState)(!1),[q,V]=(0,k.useState)(""),[Re,Se]=(0,k.useState)(!1),[J,at]=(0,k.useState)(null),[bt,Me]=(0,k.useState)([]),[ge,yr]=(0,k.useState)(0),[se,Zt]=(0,k.useState)(()=>Math.max(0,Math.min(l.length-1,Number(localStorage.getItem(s)??0)))),[ue,T]=(0,k.useState)(()=>{let v=localStorage.getItem(u);return v==="card"||v==="task"||v==="block"||v==="phase"||v==="step"||v==="snapshot"?v:"step"});(0,k.useEffect)(()=>localStorage.setItem(s,String(se)),[se,s]),(0,k.useEffect)(()=>localStorage.setItem(u,ue),[ue,u]),(0,k.useEffect)(()=>{let v=!0;return w(!1),fetch(t.href,{cache:"no-store"}).then(x=>{if(!x.ok)throw new Error(`HTTP ${x.status}`);return x.text()}).then(x=>{let R=new DOMParser().parseFromString(x,"image/svg+xml"),M=R.documentElement;if(M.nodeName.toLowerCase()!=="svg"||R.querySelector("parsererror"))throw new Error("Niepoprawny SVG");R.querySelectorAll("script, foreignObject").forEach($=>$.remove()),zp(M,t.tile),v&&c(M.outerHTML)}).catch(()=>{v&&w(!0)}),()=>{v=!1}},[t.href,t.tile]),(0,k.useEffect)(()=>{let v=!0,x=hs(t,e);if(V("Wczytywanie uk\u0142adu\u2026"),Se(!1),!t.source_href)return z(x),F(x),V("Brak \u017Ar\xF3d\u0142a PlantUML \u2014 zapis wy\u0142\u0105czony."),()=>{v=!1};let R=t.source_href.replace(/\.puml(?:\?.*)?$/,".layout.json");return fetch(`/api/uml-layout?source=${encodeURIComponent(t.source_href)}`,{cache:"no-store"}).then(async M=>{if(!M.ok)throw new Error(`HTTP ${M.status}`);return M.json()}).catch(async()=>{let M=await fetch(R,{cache:"no-store"});return M.ok?{layout:await M.json(),source_digest:"",stale:!1}:{layout:null,source_digest:"",stale:!1}}).then(M=>{if(!v)return;let $=M.layout??hs(t,e,M.source_digest??"");z($),F($),me(!1),Se(!!M.stale),V(M.layout?"Uk\u0142ad wczytany z JSON.":"Uk\u0142ad PlantUML \u2014 brak r\u0119cznych zmian.")}).catch(M=>{v&&(z(x),F(x),V(`Nie wczytano uk\u0142adu: ${M instanceof Error?M.message:String(M)}`))}),()=>{v=!1}},[t.source_href,t.label,e.block?.id,e.task?.id]),(0,k.useEffect)(()=>{let v=m.current?.querySelector("svg");if(!v||!U)return;_g(v);let x=new Map(l.map(({step:j})=>[String(j.number).padStart(2,"0"),j.id])),R=Cg(v,x),M=new Map(R.map(j=>[j.id,j]));y.current=M;let $=R.map(j=>j.id);Me(j=>j.join("\0")===$.join("\0")?j:$),R.forEach(j=>{let E=U.elements[j.id];E&&Lg(j,E)});let H=v.viewBox.baseVal;(U.canvas.width<=0||U.canvas.height<=0)&&F(j=>j&&{...j,canvas:{...j.canvas,width:H?.width||Number(v.getAttribute("width")?.replace("px",""))||0,height:H?.height||Number(v.getAttribute("height")?.replace("px",""))||0}})},[l,p,U]),(0,k.useEffect)(()=>{let v=m.current?.querySelector("svg");if(!v)return;let x=e.kind==="uml-class"&&v.contains(document.activeElement);v.setAttribute("role","img"),v.setAttribute("aria-label",t.alt),v.classList.toggle("uml-class-svg",e.kind==="uml-class"),v.classList.toggle("uml-class-svg-active",e.kind==="uml-class"&&ue!=="card"),v.querySelectorAll(".uml-step-hit, .uml-class-focus-region").forEach(N=>N.remove()),v.querySelectorAll("[data-uml-step-element]").forEach(N=>{N.classList.remove("uml-step-element","uml-step-element-active","uml-step-element-completed","uml-step-element-tone-a","uml-step-element-tone-b"),N.removeAttribute("data-uml-step-element"),N.removeAttribute("data-uml-step-claim-area")});let R=Array.from(v.querySelectorAll("text")),M=Array.from(v.querySelectorAll("line")),$=Array.from(v.querySelectorAll("polygon"));if(e.kind==="uml-class"){let N=W=>W?Eg(W):null,I=W=>{if(!W.length)return null;let B=Math.min(...W.map(ee=>ee.x)),he=Math.min(...W.map(ee=>ee.y)),ce=Math.max(...W.map(ee=>ee.x+ee.width)),Z=Math.max(...W.map(ee=>ee.y+ee.height));return{x:B,y:he,width:ce-B,height:Z-he}},K=Array.from(v.querySelectorAll("g > *")),re=Array.from(v.querySelectorAll("[id]"));l.forEach(({step:W},B)=>{let he=W.svg_label??String(W.number).padStart(2,"0"),ce=R.find(ye=>(ye.textContent??"").trim().startsWith(he));if(!ce)return;let Z=N(ce);if(!Z)return;let ee=Number(ce.getAttribute("y")),He=R.filter(ye=>Number.isFinite(ee)&&Math.abs(Number(ye.getAttribute("y"))-ee)<.8),Ie=W.svg_target?re.find(ye=>ye.id===W.svg_target||ye.id.startsWith(`${W.svg_target}-`))??null:null,Ke=N(Ie)??I(He.map(ye=>N(ye)).filter(ye=>ye!==null))??Z,St=Ie?.tagName.toLowerCase()==="rect",br=Ie?[Ie,ce]:He;St&&K.forEach(ye=>{let Jt=N(ye);if(!Jt||ye===Ie)return;let ws=Jt.x+Jt.width/2,ks=Jt.y+Jt.height/2;ws>=Ke.x&&ws<=Ke.x+Ke.width&&ks>=Ke.y&&ks<=Ke.y+Ke.height&&br.push(ye)});let Ip=!!(W.progress_id&&n?.items?.[W.progress_id]?.status==="approved"),xs=Math.max(1,Ke.width*Ke.height);br.forEach(ye=>{let Jt=Number(ye.dataset.umlStepClaimArea);Number.isFinite(Jt)&&Jt{let K=N.svg_label??String(N.number).padStart(2,"0"),re=R.find(ee=>(ee.textContent??"").trim()===K),W=Number(re?.getAttribute("y"));if(!re||!Number.isFinite(W))return;let B=W+5,he=!!(N.progress_id&&n?.items?.[N.progress_id]?.status==="approved"),ce=R.filter(ee=>Math.abs(Number(ee.getAttribute("y"))-W)<.8);ce.push(...M.filter(ee=>{let He=Number(ee.getAttribute("y1")),Ie=Number(ee.getAttribute("y2"));return Number.isFinite(He)&&Number.isFinite(Ie)&&Math.abs(He-Ie)<.8&&Math.abs((He+Ie)/2-B)<=14})),ce.push(...$.filter(ee=>{let He=(ee.getAttribute("points")??"").trim().split(/\s+/).map(St=>Number(St.split(",")[1])).filter(Number.isFinite);if(!He.length)return!1;let Ie=Math.min(...He),Ke=Math.max(...He);return Ke-Ie<=16&&Math.abs((Ie+Ke)/2-B)<=14})),ce.forEach(ee=>{ee.dataset.umlStepElement=String(I),ee.classList.add("uml-step-element"),ee.classList.toggle("uml-step-element-active",I===se),ee.classList.toggle("uml-step-element-completed",he)});let Z=document.createElementNS("http://www.w3.org/2000/svg","rect");Z.setAttribute("x",String(j)),Z.setAttribute("y",String(W-12)),Z.setAttribute("width",String(E)),Z.setAttribute("height","31"),Z.setAttribute("fill","transparent"),Z.setAttribute("pointer-events","all"),Z.setAttribute("role","button"),Z.setAttribute("tabindex","0"),Z.setAttribute("aria-label",`Poka\u017C krok ${String(N.number).padStart(2,"0")}: ${N.label}`),Z.dataset.umlStep=String(I),Z.classList.add("uml-step-hit"),Z.classList.toggle("uml-step-hit-active",ue!=="card"&&I===se),Z.classList.toggle("uml-step-hit-completed",he),v.appendChild(Z)})},[t.alt,e.kind,l,ue,n,se,p,U]),(0,k.useEffect)(()=>{let v=m.current?.querySelector("svg");if(v&&(v.querySelectorAll(".uml-layout-hit,.uml-layout-resize").forEach(x=>x.remove()),v.classList.toggle("uml-layout-editing",C),!(!C||!U)))for(let x of y.current.values()){let R=U.elements[x.id]??mo(x),M=document.createElementNS("http://www.w3.org/2000/svg","rect");M.setAttribute("x",String(R.x)),M.setAttribute("y",String(R.y)),M.setAttribute("width",String(Math.max(12,R.width))),M.setAttribute("height",String(Math.max(12,R.height))),M.setAttribute("tabindex","0"),M.setAttribute("role","button"),M.setAttribute("aria-label",`Edytuj element UML ${x.id}`),M.dataset.umlLayoutId=x.id,M.dataset.umlLayoutAction="move",M.classList.add("uml-layout-hit"),x.id===J&&M.classList.add("is-selected");let $=document.createElementNS("http://www.w3.org/2000/svg","title");if($.textContent=`${x.id} \xB7 przeci\u0105gnij, aby zmieni\u0107 po\u0142o\u017Cenie`,M.appendChild($),v.appendChild(M),x.id===J){let H=Math.max(8,Math.min(14,Math.min(R.width,R.height)*.1)),j=document.createElementNS("http://www.w3.org/2000/svg","rect");j.setAttribute("x",String(R.x+R.width-H/2)),j.setAttribute("y",String(R.y+R.height-H/2)),j.setAttribute("width",String(H)),j.setAttribute("height",String(H)),j.dataset.umlLayoutId=x.id,j.dataset.umlLayoutAction="resize",j.classList.add("uml-layout-resize"),v.appendChild(j)}}},[p,C,U,J,bt]),(0,k.useEffect)(()=>{C&&(!J||!y.current.has(J))&&at(bt[0]??null)},[C,bt,J]),(0,k.useEffect)(()=>{if(ge===0||f.current===ge)return;let v=!1,x=0,R=$=>{if(v)return;let H=m.current?.querySelector(`.uml-step-hit[data-uml-step="${se}"]`);if(!H)return;if(document.querySelector(".viewer-chrome")?.classList.contains("is-nvim-fit")){f.current=ge;return}let E=Lp(H);if(!E){f.current=ge;return}let N=xg(H);if(!N){let re=Sg(H,E),W=re===0?pp(H,E):0,B=re===0?dp(H,W):re;if($Dn){x=window.requestAnimationFrame(()=>R($+1));return}f.current=ge;return}let I=pp(H,N);if(Math.abs(I)<=Dn){f.current=ge;return}let K=dp(H,I);if($Dn){x=window.requestAnimationFrame(()=>R($+1));return}f.current=ge},M=window.requestAnimationFrame(()=>R(0));return()=>{v=!0,window.cancelAnimationFrame(M),window.cancelAnimationFrame(x)}},[ge,se,p]);let D=(v,x,R=ue,M=!0)=>{let $={task:e.task??null,block:e.block??null,phase:{id:v.phase.id,label:v.phase.label},step:v.step,focusLevel:R,activate:x&&document.documentElement.dataset.nvimSyncMode==="on"},H=()=>{window.dispatchEvent(new CustomEvent("stem:nvim-step",{detail:$}))};if(!M){H();return}let j=fetch("/api/navigation/current",{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({task:$.task,block:$.block,phase:$.phase,step:{id:$.step.id,number:$.step.number,label:$.step.label,mode:$.step.mode??($.step.snapshot_ref?"RUN":"CODE"),code_ref:$.step.code_ref??null,event_id:$.step.event_id??null,strategy_ref:$.step.strategy_ref??null},snapshot_ref:$.step.snapshot_ref??null,focus_level:$.focusLevel,sync_requested:!!$.activate,actor:document.documentElement.dataset.cardClientId??"browser"})});$.activate?j.then(H,H):(H(),j.catch(()=>{}))},G=(v,x=!1,R=ue,M=!0)=>{let $=Math.max(0,Math.min(l.length-1,v)),H=l[$];if(H){if(T(R),$===se){D(H,x,R,M);return}yr(j=>j+1),S.current=x,b.current=R,_.current=M,Zt($)}},te=(v,x)=>{let R=m.current?.querySelector("svg"),M=R?.getScreenCTM();if(!R||!M)return null;let $=R.createSVGPoint();return $.x=v,$.y=x,$.matrixTransform(M.inverse())},st=v=>{if(!C||!U)return;let x=v.target?.closest(".uml-layout-hit[data-uml-layout-id],.uml-layout-resize[data-uml-layout-id]"),R=x?.dataset.umlLayoutId,M=R?y.current.get(R):null,$=te(v.clientX,v.clientY);if(!R||!M||!$)return;v.preventDefault(),v.stopPropagation();let H=U.elements[R]??mo(M);g.current={id:R,action:x?.dataset.umlLayoutAction==="resize"?"resize":"move",pointerId:v.pointerId,startX:$.x,startY:$.y,initial:H},at(R),F(j=>j&&{...j,elements:{...j.elements,[R]:H}}),v.currentTarget.setPointerCapture(v.pointerId)},xt=v=>{let x=g.current;if(!x||x.pointerId!==v.pointerId)return;let R=te(v.clientX,v.clientY);if(!R)return;v.preventDefault();let M=R.x-x.startX,$=R.y-x.startY,H=x.action==="resize"?{...x.initial,width:Math.max(24,x.initial.width+M),height:Math.max(18,x.initial.height+$)}:{...x.initial,x:x.initial.x+M,y:x.initial.y+$};F(j=>j&&{...j,elements:{...j.elements,[x.id]:H}}),me(!0)},ze=v=>{let x=g.current;!x||x.pointerId!==v.pointerId||(g.current=null,v.currentTarget.hasPointerCapture(v.pointerId)&&v.currentTarget.releasePointerCapture(v.pointerId))},oe=(v,x)=>{let R=y.current.get(v);R&&(F(M=>{if(!M)return M;let $=M.elements[v]??mo(R);return{...M,elements:{...M.elements,[v]:x($)}}}),me(!0))},wt=async()=>{if(!U||!t.source_href)return;let v=Object.fromEntries(Array.from(y.current.values()).map(R=>[R.id,U.elements[R.id]??mo(R)])),x={...U,elements:v};V("Zapisywanie uk\u0142adu\u2026");try{let R=await fetch("/api/uml-layout",{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({source:t.source_href,layout:x,actor:document.documentElement.dataset.cardClientId??"browser"})});if(!R.ok)throw new Error((await R.json().catch(()=>null))?.message??`HTTP ${R.status}`);let M=await R.json();z(M.layout),F(M.layout),me(!1),Se(!1),V(`Zapisano ${Object.keys(M.layout.elements).length} element\xF3w w JSON.`)}catch(R){V(`B\u0142\u0105d zapisu: ${R instanceof Error?R.message:String(R)}`)}},kn=()=>{P&&(F(P),me(!1),V("Przywr\xF3cono ostatnio zapisany uk\u0142ad."))},Dt=async()=>{if(!(!t.source_href||!window.confirm("Usun\u0105\u0107 r\u0119czny uk\u0142ad i wr\xF3ci\u0107 do geometrii PlantUML?"))){V("Resetowanie uk\u0142adu\u2026");try{let v=await fetch(`/api/uml-layout?source=${encodeURIComponent(t.source_href)}&actor=${encodeURIComponent(document.documentElement.dataset.cardClientId??"browser")}`,{method:"DELETE"});if(!v.ok)throw new Error(`HTTP ${v.status}`);let x=hs(t,e,P?.source_digest??"");z(x),F(x),me(!1),at(null),V("Przywr\xF3cono automatyczny uk\u0142ad PlantUML.")}catch(v){V(`B\u0142\u0105d resetu: ${v instanceof Error?v.message:String(v)}`)}}},Q=(v,x)=>{if(C)return;let R=Array.from(m.current?.querySelectorAll(".uml-step-hit[data-uml-step]")??[]).filter($=>{let H=$.getBoundingClientRect();return v>=H.left&&v<=H.right&&x>=H.top&&x<=H.bottom}).sort(($,H)=>{let j=$.getBoundingClientRect(),E=H.getBoundingClientRect();return Math.abs(x-(j.top+j.bottom)/2)-Math.abs(x-(E.top+E.bottom)/2)}),M=Number(R[0]?.dataset.umlStep);Number.isInteger(M)&&G(M,!1,"step")},Ae=v=>{if(C||v.key!=="Enter"&&v.key!==" ")return;let x=v.target,R=Number(x.dataset?.umlStep);Number.isInteger(R)&&(v.preventDefault(),G(R,v.key==="Enter","step"))},ut=v=>{let x=l.findIndex(R=>R.phase.id===v.id);x>=0&&G(x,!1,"phase")},Ne=l[se];if((0,k.useEffect)(()=>{Ne&&(D(Ne,S.current,b.current??ue,_.current),S.current=!1,b.current=null,_.current=!0)},[Ne?.phase.id,Ne?.step.id,e.block]),(0,k.useEffect)(()=>{let v=x=>{let R=x.detail;if(!R||e.task?.id&&R.task.id!==e.task.id||e.block?.id&&R.block.id!==e.block.id)return;let M=l.findIndex($=>$.phase.id===R.phase.id&&$.step.id===R.step.id);if(M<0){T("card");return}G(M,!1,R.focus_level,!1)};return window.addEventListener("stem:navigation-command",v),()=>window.removeEventListener("stem:navigation-command",v)},[e.block?.id,e.task?.id,l,ue,se]),(0,k.useEffect)(()=>{let v=x=>{if(x.metaKey||C||x.target?.closest("input, textarea, select, [contenteditable='true']"))return;let M=d.current;if(!M)return;let $=Array.from(document.querySelectorAll("[data-stem-nav='uml-block']")),H=window.innerHeight/2;if($.sort((K,re)=>{let W=K.getBoundingClientRect(),B=re.getBoundingClientRect();return Math.abs((W.top+W.bottom)/2-H)-Math.abs((B.top+B.bottom)/2-H)})[0]!==M)return;if(x.key==="Enter"&&x.ctrlKey&&!x.altKey&&!x.shiftKey){if(!Ne?.step.progress_id||!n)return;x.preventDefault(),x.stopPropagation();let K=n.items?.[Ne.step.progress_id]?.status==="approved";r(Ne.step.progress_id,K?"pending":"approved");return}if(x.key==="Enter"&&!x.altKey&&!x.shiftKey){if(!Ne)return;x.preventDefault(),x.stopPropagation(),D(Ne,!0,ue);return}let E=["card","task","block","phase","step","snapshot"];if((x.key==="ArrowLeft"||x.key==="ArrowRight"||x.key==="Escape")&&!x.altKey&&!x.ctrlKey&&!x.shiftKey){let K=x.key==="ArrowRight"?1:-1,re=E[Math.max(0,Math.min(E.length-1,E.indexOf(ue)+K))];x.preventDefault(),x.stopPropagation(),T(re),Ne&&D(Ne,!1,re);return}if(x.key!=="ArrowUp"&&x.key!=="ArrowDown"||ue==="card"&&!x.altKey&&!x.ctrlKey&&!x.shiftKey)return;let N=x.key==="ArrowUp"?-1:1,I=ue;if(x.altKey&&x.shiftKey&&!x.ctrlKey)I="snapshot";else if(x.ctrlKey&&x.shiftKey&&!x.altKey)I="step";else if(x.altKey&&!x.ctrlKey&&!x.shiftKey)I="task";else if(x.ctrlKey&&!x.altKey&&!x.shiftKey)I="block";else if(x.shiftKey&&!x.altKey&&!x.ctrlKey)I="phase";else if(x.altKey||x.ctrlKey||x.shiftKey)return;x.preventDefault(),x.stopPropagation(),fetch("/api/navigation/move",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({level:I,delta:N,sync_requested:!1,actor:document.documentElement.dataset.cardClientId??"browser"})}).then(K=>K.ok?K.json():Promise.reject(new Error(`HTTP ${K.status}`))).then(K=>{K.current&&window.dispatchEvent(new CustomEvent("stem:navigation-command",{detail:K.current}))}).catch(()=>{})};return window.addEventListener("keydown",v),()=>window.removeEventListener("keydown",v)},[Ne,l,ue,r,n,C]),!Ne)return null;let{phase:Ee,step:vr}=Ne,kt=l.filter(v=>v.phase.id===Ee.id),yo=e.block?.label.match(/^A[1-8]\b/)?.[0]??"UML",vo=(e.title??e.block?.label??t.caption).replace(/^A[1-8]\s*[·—:-]\s*/i,""),jn=J?y.current.get(J)??null:null,ct=jn?U?.elements[jn.id]??mo(jn):null;return(0,a.jsxs)("div",{ref:d,className:"uml-react uml-snapshot-react","data-stem-nav":"uml-block","data-task-id":e.task?.id,"data-block-id":e.block?.id,"data-navigation-level":ue,children:[(0,a.jsx)("header",{className:"uml-snapshot-header",children:(0,a.jsxs)("div",{className:"uml-header-first-line",children:[(0,a.jsxs)("h3",{children:[(0,a.jsx)("span",{children:yo})," \u2014 ",vo,t.tile&&(0,a.jsxs)("small",{className:"uml-tile-part",children:["cz\u0119\u015B\u0107 ",t.tile.index,"/",t.tile.count," \xB7 r",t.tile.row," c",t.tile.column]})]}),(0,a.jsxs)("div",{className:"uml-header-navigation",children:[(0,a.jsx)(Pp,{continuation:o}),t.source_href&&(0,a.jsx)("button",{className:`uml-edit-toggle${C?" is-active":""}`,"aria-pressed":C,title:"R\u0119czna edycja po\u0142o\u017Cenia i parametr\xF3w blok\xF3w UML",onClick:()=>A(v=>!v),children:C?"ZAMKNIJ EDIT":"EDIT"}),(0,a.jsx)("div",{className:"uml-phase-actions","aria-label":"Fazy diagramu",children:i.map(v=>(0,a.jsx)("button",{"aria-pressed":Ee.id===v.id,onClick:()=>ut(v),children:v.label},v.id))}),(0,a.jsx)("div",{className:"uml-step-actions","aria-label":`Checkpointy fazy ${Ee.label}`,children:kt.map(v=>{let x=l.indexOf(v),R=!!(v.step.progress_id&&n?.items?.[v.step.progress_id]?.status==="approved");return(0,a.jsx)("button",{"aria-label":`Krok ${v.step.number}: ${v.step.label}`,"aria-pressed":ue!=="card"&&x===se,"data-completed":R?"true":"false","data-mode":v.step.mode??"RUN",title:`${v.step.mode??"RUN"} \xB7 ${v.step.label}`,onClick:()=>G(x),children:String(v.step.number).padStart(2,"0")},v.step.id)})})]})]})}),(0,a.jsxs)("div",{className:"uml-snapshot-layout",children:[(0,a.jsxs)("div",{ref:m,className:`uml-canvas uml-step-canvas${C?" is-layout-editing":""}`,onClick:v=>Q(v.clientX,v.clientY),onKeyDown:Ae,onPointerDown:st,onPointerMove:xt,onPointerUp:ze,onPointerCancel:ze,children:[p?(0,a.jsx)("div",{className:`uml-svg-host${t.tile?" is-tiled":""}`,style:vs(t.tile),dangerouslySetInnerHTML:{__html:p}}):(0,a.jsx)("img",{src:t.href,alt:t.alt}),h&&(0,a.jsx)("span",{className:"uml-inline-warning",children:"Widok statyczny \u2014 wybierz krok przyciskiem nad diagramem."})]}),C&&U&&(0,a.jsxs)("aside",{className:"uml-layout-editor","aria-label":"Edytor uk\u0142adu UML",children:[(0,a.jsxs)("div",{className:"uml-layout-editor-title",children:[(0,a.jsx)("strong",{children:"UML layout"}),(0,a.jsx)("span",{children:ae?"niezapisane zmiany":"zapisany"}),Re&&(0,a.jsx)("b",{children:"\u0179r\xF3d\u0142o PlantUML zmieni\u0142o si\u0119"})]}),(0,a.jsxs)("label",{children:["Element",(0,a.jsx)("select",{value:J??"",onChange:v=>at(v.target.value||null),children:bt.map(v=>(0,a.jsx)("option",{value:v,children:v},v))})]}),J&&ct&&(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("div",{className:"uml-layout-number-grid",children:["x","y","width","height"].map(v=>(0,a.jsxs)("label",{children:[v,(0,a.jsx)("input",{type:"number",step:"1",min:v==="width"||v==="height"?12:void 0,value:Math.round(ct[v]*100)/100,onChange:x=>oe(J,R=>({...R,[v]:Number(x.target.value)}))})]},v))}),(0,a.jsxs)("label",{className:"uml-layout-editor-text",children:["Tekst bloku \u2014 jeden wiersz SVG na lini\u0119",(0,a.jsx)("textarea",{rows:Math.max(3,Math.min(10,ct.text?.lines?.length??3)),value:(ct.text?.lines??[]).join(` +`),onChange:v=>oe(J,x=>({...x,text:{...x.text,title:v.target.value.split(` +`)[0]??"",description:v.target.value.split(` +`).slice(1).join(` +`),lines:v.target.value.split(` +`)}}))})]}),(0,a.jsxs)("div",{className:"uml-layout-style-grid",children:[(0,a.jsxs)("label",{children:["Font",(0,a.jsx)("input",{type:"number",min:"4",max:"96",step:"0.5",value:ct.text?.font_size??12,onChange:v=>oe(J,x=>({...x,text:{...x.text,font_size:Number(v.target.value)}}))})]}),(0,a.jsxs)("label",{children:["Wyr\xF3wnanie",(0,a.jsxs)("select",{value:ct.text?.align??"left",onChange:v=>oe(J,x=>({...x,text:{...x.text,align:v.target.value}})),children:[(0,a.jsx)("option",{value:"left",children:"left"}),(0,a.jsx)("option",{value:"center",children:"center"}),(0,a.jsx)("option",{value:"right",children:"right"})]})]}),(0,a.jsxs)("label",{children:["Wype\u0142nienie",(0,a.jsx)("input",{type:"color",value:ct.style?.fill??"#edf6fa",onChange:v=>oe(J,x=>({...x,style:{...x.style,fill:v.target.value}}))})]}),(0,a.jsxs)("label",{children:["Obramowanie",(0,a.jsx)("input",{type:"color",value:ct.style?.stroke??"#2c7794",onChange:v=>oe(J,x=>({...x,style:{...x.style,stroke:v.target.value}}))})]}),(0,a.jsxs)("label",{children:["Linia",(0,a.jsx)("input",{type:"number",min:"0.5",max:"12",step:"0.25",value:ct.style?.stroke_width??1.5,onChange:v=>oe(J,x=>({...x,style:{...x.style,stroke_width:Number(v.target.value)}}))})]})]})]}),(0,a.jsxs)("div",{className:"uml-layout-editor-actions",children:[(0,a.jsx)("button",{className:"is-primary",disabled:!ae,onClick:()=>void wt(),children:"Zapisz uk\u0142ad"}),(0,a.jsx)("button",{disabled:!ae,onClick:kn,children:"Cofnij zmiany"}),(0,a.jsx)("button",{className:"is-danger",onClick:()=>void Dt(),children:"Reset PlantUML"})]}),(0,a.jsx)("output",{children:q})]})]})]})}function nh(e){return e.config.phases?.length?(0,a.jsx)(th,{...e}):(0,a.jsx)(eh,{...e})}function rh({asset:e,continuation:t}){let n=e.tile;if(!n)return(0,a.jsx)("img",{src:e.href,alt:e.alt,loading:"lazy"});let r=e.diagram_block_label?.match(/^A[1-8]\b/)?.[0]??e.diagram_title?.match(/^A[1-8]\b/)?.[0]??e.caption.match(/^A[1-8]\b/)?.[0]??"UML",o=(e.diagram_title??e.caption).replace(/^A[1-8]\s*[·—:-]\s*/i,"");return(0,a.jsxs)("div",{className:"uml-react uml-snapshot-react uml-static-tile",children:[(0,a.jsx)("header",{className:"uml-snapshot-header",children:(0,a.jsxs)("div",{className:"uml-header-first-line",children:[(0,a.jsxs)("h3",{children:[(0,a.jsx)("span",{children:r})," \u2014 ",o,(0,a.jsxs)("small",{className:"uml-tile-part",children:["cz\u0119\u015B\u0107 ",n.index,"/",n.count," \xB7 r",n.row," c",n.column]})]}),(0,a.jsxs)("div",{className:"uml-header-navigation",children:[(0,a.jsx)(Pp,{continuation:t}),(0,a.jsx)("div",{className:"uml-phase-actions","aria-label":"Fazy logicznego diagramu",children:(e.diagram_phase_labels??[]).map(i=>(0,a.jsx)("span",{children:i},i))})]})]})}),(0,a.jsx)("div",{className:"uml-snapshot-layout",children:(0,a.jsx)("div",{className:"uml-canvas uml-step-canvas",children:(0,a.jsx)("div",{className:"uml-svg-host is-tiled is-static",style:vs(n),children:(0,a.jsx)("div",{className:"uml-static-crop",style:{width:`${n.width}px`,height:`${n.height}px`},children:(0,a.jsx)("img",{src:e.href,alt:e.alt,style:{width:`${n.full_width}px`,height:`${n.full_height}px`,transform:`translate(${-(n.x-n.full_x)}px, ${-(n.y-n.full_y)}px)`}})})})})})]})}function oh({asset:e,number:t,progress:n,onSetStatus:r,continuation:o}){let i=e.interactive?.kind,l=!!i;return(0,a.jsxs)("figure",{className:`card-asset asset-${e.kind} ${l?"asset-interactive":""}`,id:e.label.replace(":","-"),children:[i==="uml-sequence"||i==="uml-class"?(0,a.jsx)(nh,{config:e.interactive,asset:e,progress:n,onSetStatus:r,continuation:o}):i==="allocator-memory-flow"?(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(Jg,{config:e.interactive,title:e.caption}),(0,a.jsx)("img",{className:"asset-static-fallback",src:e.href,alt:e.alt})]}):e.tile?(0,a.jsx)(rh,{asset:e,continuation:o}):(0,a.jsx)("a",{href:e.href,target:"_blank",rel:"noopener",children:(0,a.jsx)("img",{src:e.href,alt:e.alt,loading:"lazy",style:{width:`${e.width*100}%`}})}),(0,a.jsxs)("figcaption",{children:["Rys. ",e.figure_number??t,". ",e.caption]}),e.diagram_description&&(0,a.jsxs)("aside",{className:"uml-after-description",children:[(0,a.jsx)("strong",{children:"Interpretacja"}),(0,a.jsx)("p",{children:e.diagram_description})]})]})}function ih({steps:e,progress:t,onCycle:n}){if(!e.length)return null;let r={pending:"\u25CB niezatwierdzony",approved:"\u25CF zatwierdzony"};return(0,a.jsx)("div",{className:"step-map",children:e.map(o=>{let i=t?.items?.[o.id]?.status??"pending";return(0,a.jsxs)("div",{className:`step-row ${i==="approved"?"lesson-progress-completed":""}`,children:[(0,a.jsx)("button",{className:"lesson-progress-control","data-status":i,onClick:()=>n(o.id,i),disabled:!t,children:r[i]}),(0,a.jsx)("span",{className:"step-title",children:o.title})]},o.id)})})}function lh({section:e,progress:t,onCycle:n,onSetStatus:r,figureOffset:o}){let i=Pg(e);return(0,a.jsxs)(bs,{header:e.header_html,footer:e.footer_html,left:e.left_margin_html,right:e.right_margin_html,className:`section-sheet${e.assets.length?" section-sheet--asset":""} sheet--${e.page_orientation||"portrait"}`,children:[(0,a.jsx)("div",{id:e.id,className:"section-anchor"}),e.show_heading&&(0,a.jsxs)("h2",{className:"section-heading",children:[(0,a.jsx)("span",{className:"section-index",children:e.index}),(0,a.jsx)("span",{className:"section-title",children:e.title})]}),(0,a.jsx)(ih,{steps:e.steps,progress:t,onCycle:n}),e.content_html&&(0,a.jsx)(Ft,{html:e.content_html}),(0,a.jsx)("div",{className:"tasks",children:e.tasks.map(l=>(0,a.jsx)(Zg,{task:l},l.ref))}),e.assets.map((l,s)=>(0,a.jsx)(oh,{asset:l,number:o+s+1,progress:t,onSetStatus:r,continuation:i},l.label)),e.tasks.map(l=>l.conclusion_html&&(0,a.jsxs)("aside",{className:"task-conclusion",children:[(0,a.jsx)("strong",{children:"WNIOSEK"}),(0,a.jsx)(Ft,{html:l.conclusion_html})]},`${l.ref}-conclusion`))]})}function ah({progress:e,error:t}){if(!e?.summary&&!t)return null;let n=e?.summary.counts;return(0,a.jsxs)("section",{className:"lesson-progress-topbar","aria-label":"Przebieg zaj\u0119\u0107",children:[(0,a.jsx)("strong",{children:"PRZEBIEG"}),n&&(0,a.jsxs)("output",{title:"Zatwierdzone kroki",children:[n.approved,"/",e.summary.total]}),t&&(0,a.jsx)("span",{className:"lesson-progress-error",title:t,children:"!"}),(0,a.jsx)("a",{href:"/api/report/teams.md",target:"_blank",rel:"noopener",title:"Otw\xF3rz raport do Teams",children:"Teams"}),(0,a.jsx)("a",{href:"/api/report/lesson.html",target:"_blank",rel:"noopener",title:"Otw\xF3rz \u015Blad lekcji ze zrzutami",children:"\u015Alad"})]})}var Fn=[{id:"u01",name:"Alicja Nowak",laptop:3,connection:"online",sync:"follow",position:"A5\xB708"},{id:"u02",name:"Bartosz Kowalski",laptop:7,connection:"online",sync:"follow",position:"A5\xB708"},{id:"u03",name:"Celina Wi\u015Bniewska",laptop:11,connection:"away",sync:"paused",position:"A5\xB705"},{id:"u04",name:"Dawid W\xF3jcik",laptop:14,connection:"offline",sync:"follow",position:"\u2014"},{id:"u05",name:"Emilia Kami\u0144ska",laptop:18,connection:"online",sync:"local",position:"A3\xB702"},{id:"u06",name:"Filip Lewandowski",laptop:22,connection:"error",sync:"follow",position:"A5\xB706"},{id:"u07",name:"Gabriela Zieli\u0144ska",laptop:24,connection:"online",sync:"follow",position:"A5\xB708"}];function sh(e){let t=new Map,n=(r,o)=>{let i=r.trim()||"task",l=t.get(i);return l||(l={id:i,label:o.trim()||i.toUpperCase(),blocks:[],exercises:[]},t.set(i,l)),l};for(let r of e.sections){for(let o of r.assets){let i=o.interactive;if(!i?.task||!i.block)continue;let l=n(i.task.id,i.task.label),s=`diagram:${i.block.id}`,u=l.blocks.find(d=>d.id===s);u||(u={id:s,label:i.block.label,kind:"diagram",phases:[],steps:[]},l.blocks.push(u));for(let d of i.phases??[]){let m=u.phases.find(y=>y.id===d.id);m||(m={...d,steps:[]},u.phases.push(m));for(let y of d.steps)m.steps.some(g=>g.id===y.id)||m.steps.push(y)}}for(let o of r.tasks){let i=n(o.ref,o.title);i.label=o.title||i.label;for(let l of o.flow){if(l.kind==="exercise"){i.exercises.some(u=>u.id===l.id)||i.exercises.push(l);continue}let s=`flow:${l.id}`;i.blocks.some(u=>u.id===s)||i.blocks.push({id:s,label:l.title,kind:"flow",phases:[],steps:l.steps.map((u,d)=>({id:u.id,number:d+1,label:u.title,description:u.title,mode:"RUN"}))})}}}return[...t.values()]}function bp({side:e,panels:t,activeId:n,pinnedId:r,onActiveChange:o,onPinnedChange:i}){let l=(0,k.useRef)(0),s=t.find(f=>f.id===n)??null,u=(0,k.useCallback)(()=>{window.clearTimeout(l.current)},[]),d=(0,k.useCallback)(()=>{u(),!r&&(l.current=window.setTimeout(()=>o(null),150))},[u,o,r]),m=(0,k.useCallback)(f=>{u(),r||o(f)},[u,o,r]),y=(0,k.useCallback)(f=>{if(u(),r===f){i(null),o(null);return}i(f),o(f)},[u,o,i,r]),g=(0,k.useCallback)(()=>{u(),i(null),o(null)},[u,o,i]);return(0,k.useEffect)(()=>()=>window.clearTimeout(l.current),[]),t.length?(0,a.jsxs)("div",{className:`side-panel-dock side-panel-dock--${e}`,"data-side":e,children:[(0,a.jsx)("nav",{className:"side-panel-rail","aria-label":`Panele ${e==="left"?"lewe":"prawe"}`,children:t.map(f=>{let S=n===f.id,b=r===f.id;return(0,a.jsx)("button",{type:"button",className:`side-panel-trigger${S?" is-open":""}${b?" is-pinned":""}`,style:{"--side-panel-accent":f.accent},"aria-controls":`side-panel-${e}-${f.id}`,"aria-expanded":S,"aria-pressed":b,"aria-label":`${f.title}: ${b?"odpnij":"przypnij"} panel`,title:`${f.title} \xB7 ${b?"Fixed":"Auto"}`,onMouseEnter:()=>m(f.id),onMouseLeave:d,onFocus:()=>m(f.id),onClick:()=>y(f.id),children:(0,a.jsx)("span",{children:f.railLabel})},f.id)})}),(0,a.jsx)("aside",{id:s?`side-panel-${e}-${s.id}`:void 0,className:`side-panel${s?" is-open":""}`,"aria-hidden":!s,"aria-label":s?.title,style:s?{"--side-panel-accent":s.accent}:void 0,onMouseEnter:u,onMouseLeave:d,onFocusCapture:u,children:s&&(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)("header",{className:"side-panel-header",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)("small",{children:e==="left"?"LEWY PANEL":"PRAWY PANEL"}),(0,a.jsx)("strong",{children:s.title})]}),(0,a.jsxs)("div",{className:"side-panel-actions",children:[(0,a.jsx)("button",{type:"button",className:r===s.id?"is-pinned":"",onClick:()=>y(s.id),title:r===s.id?"Tryb Fixed \u2014 kliknij, aby przej\u015B\u0107 do Auto":"Tryb Auto \u2014 kliknij, aby przypi\u0105\u0107",children:r===s.id?"Fixed":"Auto"}),(0,a.jsx)("button",{type:"button",onClick:g,"aria-label":`Zamknij ${s.title}`,children:"\xD7"})]})]}),(0,a.jsx)("div",{className:"side-panel-body",children:s.content})]})})]}):null}function go({id:e,depth:t,kind:n,title:r,label:o,refValue:i,stateValue:l,stateKind:s,hasChildren:u=!1,expanded:d=!1,onToggle:m,href:y,current:g=!1,unavailable:f=!1,onNavigate:S}){let b=y?(0,a.jsx)("a",{className:"card-library-tree-link","data-stem-source-aware":"true",href:y,onClick:S,"aria-current":g?"page":void 0,title:g?"Poka\u017C bie\u017C\u0105c\u0105 kart\u0119":`Otw\xF3rz: ${r}`,children:o}):(0,a.jsx)("span",{className:"card-library-tree-label",title:r,children:o});return(0,a.jsxs)("div",{className:`card-library-treegrid-row is-${n}${g?" is-current":""}${f?" is-unavailable":""}`,role:"row","aria-level":t+1,"aria-expanded":u?d:void 0,"aria-disabled":f||void 0,"data-node-id":e,style:{"--card-library-depth":t},children:[(0,a.jsxs)("span",{className:"card-library-tree-title",role:"gridcell",children:[u?(0,a.jsx)("button",{className:"card-library-tree-expander",type:"button",onClick:m,"aria-label":`${d?"Zwi\u0144":"Rozwi\u0144"}: ${r}`,children:d?"\u25BE":"\u25B8"}):(0,a.jsx)("span",{className:"card-library-tree-expander is-empty","aria-hidden":"true"}),b]}),(0,a.jsx)("span",{role:"gridcell",children:i}),(0,a.jsx)("span",{role:"gridcell",children:(0,a.jsx)("b",{className:"card-library-tree-state","data-state":s,children:l})})]})}function uh({library:e,source:t,onNavigate:n}){if(!e)return null;let r=e.subjects.reduce((m,y)=>m+y.levels.reduce((g,f)=>g+f.series.length,0),0),o=e.subjects.reduce((m,y)=>m+y.levels.reduce((g,f)=>g+f.series.reduce((S,b)=>S+b.cards.filter(_=>_.available).length,0),0),0),i=e.subjects.reduce((m,y)=>m+y.levels.reduce((g,f)=>g+f.series.reduce((S,b)=>S+b.cards.length,0),0),0),[l,s]=(0,k.useState)(()=>{let m=new Set;return e.subjects.forEach(y=>{let g=`subject:${y.id}`;y.current&&m.add(g),y.levels.forEach(f=>{let S=`${g}:level:${f.id}`;f.current&&m.add(S),f.series.forEach(b=>{let _=`${S}:series:${b.id}`;b.current&&m.add(_),b.cards.forEach(p=>{p.current&&(p.tasks?.length??0)>0&&m.add(`${_}:card:${p.id}`)})})})}),m}),u=m=>l.has(m),d=m=>{s(y=>{let g=new Set(y);return g.has(m)?g.delete(m):g.add(m),g})};return(0,a.jsxs)("div",{className:"card-library-panel",children:[(0,a.jsxs)("div",{className:"card-library-treegrid",role:"treegrid","aria-label":"Serie, karty i taski","aria-colcount":3,children:[(0,a.jsxs)("div",{className:"card-library-treegrid-row card-library-treegrid-head",role:"row",children:[(0,a.jsx)("span",{role:"columnheader",children:"DRZEWO"}),(0,a.jsx)("span",{role:"columnheader",children:"REF"}),(0,a.jsx)("span",{role:"columnheader",children:"STATUS"})]}),e.subjects.map(m=>{let y=`subject:${m.id}`;return(0,a.jsxs)(k.default.Fragment,{children:[(0,a.jsx)(go,{id:y,depth:0,kind:"subject",title:m.title,label:(0,a.jsx)("strong",{children:m.title}),refValue:"PRZEDMIOT",stateValue:`${m.levels.length} POZ.`,hasChildren:m.levels.length>0,expanded:u(y),onToggle:()=>d(y)}),u(y)&&m.levels.map(g=>{let f=`${y}:level:${g.id}`;return(0,a.jsxs)(k.default.Fragment,{children:[(0,a.jsx)(go,{id:f,depth:1,kind:"level",title:g.title,label:(0,a.jsx)("strong",{children:g.title}),refValue:"ROK",stateValue:`${g.series.length} SERII`,hasChildren:g.series.length>0,expanded:u(f),onToggle:()=>d(f)}),u(f)&&g.series.map(S=>{let b=`${f}:series:${S.id}`;return(0,a.jsxs)(k.default.Fragment,{children:[(0,a.jsx)(go,{id:b,depth:2,kind:"series",title:S.title,label:(0,a.jsx)("strong",{children:S.title}),refValue:S.role==="reference"?"REF.":"SERIA",stateValue:`${S.cards.length} KART`,hasChildren:S.cards.length>0,expanded:u(b),onToggle:()=>d(b)}),u(b)&&S.cards.map(_=>{let p=`${b}:card:${_.id}`,c=_.tasks??[],h=_.current||_.available,w=vg(_,t),C=_.current?"current":_.available?"online":"plan";return(0,a.jsxs)(k.default.Fragment,{children:[(0,a.jsx)(go,{id:p,depth:3,kind:"card",title:`${_.id} \xB7 ${_.title}`,label:(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("strong",{children:_.id}),(0,a.jsx)("span",{children:_.title})]}),refValue:_.version||"\u2014",stateValue:_.current?"BIE\u017B\u0104CA":_.available?"ONLINE":"PLAN",stateKind:C,hasChildren:c.length>0,expanded:u(p),onToggle:()=>d(p),href:h?_.current?"#":w:void 0,current:_.current,unavailable:!h,onNavigate:n}),u(p)&&c.map(A=>{let P=_.current?`#${A.id}`:`${w}#${A.id}`;return(0,a.jsx)(go,{id:`${p}:task:${A.id}`,depth:4,kind:"task",title:`${A.id.toUpperCase()} \xB7 ${A.title}`,label:(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("strong",{children:A.id.toUpperCase()}),(0,a.jsx)("span",{children:A.title})]}),refValue:"TASK",stateValue:h?"GOTOWY":"PLAN",stateKind:h?"online":"plan",href:h?P:void 0,unavailable:!h,onNavigate:n},`${p}:task:${A.id}`)})]},p)})]},b)})]},f)})]},y)})]}),(0,a.jsxs)("footer",{className:"card-library-footer",children:[(0,a.jsx)("span",{children:e.workspace||"STEM"}),(0,a.jsxs)("span",{children:[r," serii"]}),(0,a.jsxs)("span",{children:[(0,a.jsx)("i",{className:"is-online"})," ",o,"/",i," online"]}),(0,a.jsx)("kbd",{children:"Esc"})]})]})}function ch({toc:e,onNavigate:t}){return(0,a.jsxs)("nav",{className:"side-toc","aria-label":"Spis tre\u015Bci karty",children:[(0,a.jsxs)("header",{children:[(0,a.jsx)("small",{children:"KARTA PRACY"}),(0,a.jsx)("h2",{children:"Spis tre\u015Bci"})]}),(0,a.jsx)("ol",{children:e.map((n,r)=>(0,a.jsx)("li",{children:(0,a.jsxs)("a",{href:`#${n.id}`,onClick:t,children:[(0,a.jsx)("span",{children:String(r+1).padStart(2,"0")}),(0,a.jsx)("strong",{children:n.label})]})},n.id))})]})}function dh({progress:e,error:t}){let n=e?.summary?.counts;return(0,a.jsxs)("section",{className:"side-progress","aria-label":"Przebieg zaj\u0119\u0107 i raport",children:[(0,a.jsxs)("header",{children:[(0,a.jsx)("small",{children:"DOWODY I RAPORT"}),(0,a.jsx)("h2",{children:"Przebieg zaj\u0119\u0107"})]}),(0,a.jsxs)("div",{className:"side-progress-summary",children:[(0,a.jsx)("span",{children:"Zatwierdzone kroki"}),(0,a.jsx)("strong",{children:n?`${n.approved}/${e.summary.total}`:"\u2014"})]}),t&&(0,a.jsx)("p",{className:"side-progress-error",children:t}),(0,a.jsxs)("div",{className:"side-progress-links",children:[(0,a.jsx)("a",{href:"/api/report/teams.md",target:"_blank",rel:"noopener",children:"Raport do Teams"}),(0,a.jsx)("a",{href:"/api/report/lesson.html",target:"_blank",rel:"noopener",children:"\u015Alad lekcji ze zrzutami"})]})]})}var ph={online:"ONLINE",away:"ZAJ\u0118TY",offline:"OFFLINE",error:"B\u0141\u0104D"},mh={follow:"SYNC",paused:"PAUZA",local:"LOKAL."},xp={present:"BY\u0141",missed:"NIE BY\u0141",pending:"OCZEK."},fh={committed:"COMMIT",working:"PRACA",missing:"BRAK"};function Mp(e){let t=2166136261;for(let n=0;n>>0}function wp(e,t,n){let r=Mp(`${e.id}:step:${t}`),o=r%8,i=o===0?"missed":o===1?"pending":"present",l=8+n*4+r%3,s=10+Math.floor(l/60),u=l%60,d=(e.laptop*7+r)%60;return{state:i,timestamp:i==="present"?`${String(s).padStart(2,"0")}:${String(u).padStart(2,"0")}:${String(d).padStart(2,"0")}`:"\u2014"}}function kp(e,t){let n=Mp(`${e.id}:exercise:${t}`),r=n%6,o=r===0?"missing":r===1?"working":"committed";return{state:o,commit:o==="committed"?n.toString(16).padStart(8,"0").slice(0,7):"\u2014"}}function Xt({id:e,depth:t,title:n,kind:r,refValue:o,session:i,evidence:l,state:s,hasChildren:u=!1,expanded:d=!1,onToggle:m}){return(0,a.jsxs)("div",{className:`classroom-treegrid-row classroom-node-row is-${r}`,role:"row","aria-level":t+1,"aria-expanded":u?d:void 0,"data-node-id":e,style:{"--classroom-depth":t},children:[(0,a.jsxs)("span",{className:"classroom-node-title",role:"gridcell",title:n,children:[u?(0,a.jsx)("button",{className:"classroom-expander",type:"button",onClick:m,"aria-label":`${d?"Zwi\u0144":"Rozwi\u0144"}: ${n}`,children:d?"\u25BE":"\u25B8"}):(0,a.jsx)("span",{className:"classroom-expander is-empty","aria-hidden":"true"}),(0,a.jsx)("strong",{children:n})]}),(0,a.jsx)("span",{role:"gridcell",children:o}),(0,a.jsx)("span",{role:"gridcell",children:i}),(0,a.jsx)("span",{role:"gridcell",children:l}),(0,a.jsx)("span",{role:"gridcell",children:s})]})}function gh({model:e}){let t=sh(e),n=Fn[0]?.id,r=t[0]?.id,[o,i]=(0,k.useState)(()=>new Set(["class",...n?[`student:${n}`]:[],...n&&r?[`student:${n}:task:${r}`]:[]])),l=m=>o.has(m),s=m=>{i(y=>{let g=new Set(y);return g.has(m)?g.delete(m):g.add(m),g})},u=Fn.filter(m=>m.connection==="online"||m.connection==="away").length,d=Fn.filter(m=>m.sync==="follow").length;return(0,a.jsxs)("section",{className:"classroom-panel","aria-label":"Klasa i stanowiska uczni\xF3w",children:[(0,a.jsxs)("div",{className:"classroom-treegrid",role:"treegrid","aria-label":"Uczniowie, struktura karty, obecno\u015B\u0107 i commity","aria-colcount":5,children:[(0,a.jsxs)("div",{className:"classroom-treegrid-row classroom-treegrid-head",role:"row",children:[(0,a.jsx)("span",{role:"columnheader",children:"DRZEWO"}),(0,a.jsx)("span",{role:"columnheader",children:"REF"}),(0,a.jsx)("span",{role:"columnheader",children:"SESJA"}),(0,a.jsx)("span",{role:"columnheader",children:"DOW\xD3D"}),(0,a.jsx)("span",{role:"columnheader",children:"STAN"})]}),(0,a.jsx)(Xt,{id:"class",depth:0,kind:"class",title:"2TP \xB7 grupa A",refValue:`${Fn.length} ucz.`,session:`${u}/${Fn.length}`,evidence:`${d}/${Fn.length} SYNC`,state:`${t.length} TASK`,hasChildren:!0,expanded:l("class"),onToggle:()=>s("class")}),l("class")&&Fn.map(m=>{let y=`student:${m.id}`;return(0,a.jsxs)(k.default.Fragment,{children:[(0,a.jsx)(Xt,{id:y,depth:1,kind:"student",title:m.name,refValue:`L${String(m.laptop).padStart(2,"0")}`,session:(0,a.jsx)("b",{className:"classroom-status","data-status":m.connection,children:ph[m.connection]}),evidence:(0,a.jsx)("b",{className:"classroom-sync","data-sync":m.sync,children:mh[m.sync]}),state:m.position,hasChildren:t.length>0,expanded:l(y),onToggle:()=>s(y)}),l(y)&&t.map(g=>{let f=`${y}:task:${g.id}`,S=g.exercises.filter(b=>kp(m,b.id).state==="committed").length;return(0,a.jsxs)(k.default.Fragment,{children:[(0,a.jsx)(Xt,{id:f,depth:2,kind:"task",title:g.label,refValue:g.id.toUpperCase(),session:`${g.blocks.length} BLOCK`,evidence:`${S}/${g.exercises.length} COMMIT`,state:"TASK",hasChildren:g.blocks.length+g.exercises.length>0,expanded:l(f),onToggle:()=>s(f)}),l(f)&&g.blocks.map(b=>{let _=`${f}:block:${b.id}`,p=b.phases.length||b.steps.length;return(0,a.jsxs)(k.default.Fragment,{children:[(0,a.jsx)(Xt,{id:_,depth:3,kind:"block",title:b.label,refValue:b.kind==="diagram"?"BLOCK":"TASK BLOCK",session:`${p} ${b.phases.length?"PHASE":"STEP"}`,evidence:"\u2014",state:b.kind==="diagram"?"UML":"FLOW",hasChildren:p>0,expanded:l(_),onToggle:()=>s(_)}),l(_)&&b.phases.map(c=>{let h=`${_}:phase:${c.id}`;return(0,a.jsxs)(k.default.Fragment,{children:[(0,a.jsx)(Xt,{id:h,depth:4,kind:"phase",title:`PHASE \xB7 ${c.label}`,refValue:`${c.steps.length} STEP`,session:"\u2014",evidence:"\u2014",state:"PHASE",hasChildren:c.steps.length>0,expanded:l(h),onToggle:()=>s(h)}),l(h)&&c.steps.map(w=>{let C=wp(m,`${b.id}:${c.id}:${w.id}`,w.number);return(0,a.jsx)(Xt,{id:`${h}:step:${w.id}`,depth:5,kind:"step",title:`STEP ${String(w.number).padStart(2,"0")} \xB7 ${w.label}`,refValue:w.mode??"CODE",session:(0,a.jsx)("b",{className:"classroom-visit","data-visit":C.state,children:xp[C.state]}),evidence:C.timestamp,state:C.state==="present"?"OK":C.state==="missed"?"BRAK":"\u2014"},`${h}:step:${w.id}`)})]},h)}),l(_)&&b.steps.map(c=>{let h=wp(m,`${b.id}:${c.id}`,c.number);return(0,a.jsx)(Xt,{id:`${_}:step:${c.id}`,depth:4,kind:"step",title:`STEP ${String(c.number).padStart(2,"0")} \xB7 ${c.label}`,refValue:c.mode??"RUN",session:(0,a.jsx)("b",{className:"classroom-visit","data-visit":h.state,children:xp[h.state]}),evidence:h.timestamp,state:h.state==="present"?"OK":h.state==="missed"?"BRAK":"\u2014"},`${_}:step:${c.id}`)})]},_)}),l(f)&&g.exercises.length>0&&(()=>{let b=`${f}:exercises`;return(0,a.jsxs)(k.default.Fragment,{children:[(0,a.jsx)(Xt,{id:b,depth:3,kind:"exercises",title:"EXERCISES",refValue:`${g.exercises.length} EX`,session:`${S}/${g.exercises.length}`,evidence:"GIT",state:S===g.exercises.length?"DONE":"TODO",hasChildren:!0,expanded:l(b),onToggle:()=>s(b)}),l(b)&&g.exercises.map(_=>{let p=kp(m,_.id);return(0,a.jsx)(Xt,{id:`${b}:${_.id}`,depth:4,kind:"exercise",title:`EXERCISE \xB7 ${_.title}`,refValue:_.id,session:(0,a.jsx)("b",{className:"classroom-commit","data-commit":p.state,children:fh[p.state]}),evidence:p.commit,state:p.state==="committed"?"DONE":p.state==="working"?"W TOKU":"TODO"},`${b}:${_.id}`)})]},b)})()]},f)})]},m.id)})]}),(0,a.jsxs)("footer",{className:"classroom-legend",children:[(0,a.jsxs)("span",{children:[(0,a.jsx)("b",{children:"BY\u0141"})," obecny przy omawianym STEP"]}),(0,a.jsxs)("span",{children:[(0,a.jsx)("b",{children:"COMMIT"})," rozwi\u0105zanie EXERCISE zapisane w Git"]})]})]})}function hh({open:e,onClose:t}){return e?(0,a.jsx)("div",{className:"shortcut-help-backdrop",role:"presentation",onMouseDown:t,children:(0,a.jsxs)("section",{className:"shortcut-help",role:"dialog","aria-modal":"true","aria-labelledby":"shortcut-help-title",onMouseDown:r=>r.stopPropagation(),children:[(0,a.jsxs)("header",{children:[(0,a.jsxs)("div",{children:[(0,a.jsx)("small",{children:"F12 \xB7 NAWIGACJA DYDAKTYCZNA"}),(0,a.jsx)("h2",{id:"shortcut-help-title",children:"CARD \u2192 TASK \u2192 BLOCK \u2192 PHASE \u2192 STEP \u2192 SNAPSHOT"})]}),(0,a.jsx)("button",{onClick:t,"aria-label":"Zamknij skorowidz",children:"\xD7"})]}),(0,a.jsx)("dl",{children:[["Alt + \u2191 / \u2193","poprzedni / nast\u0119pny TASK"],["Ctrl + \u2191 / \u2193","poprzedni / nast\u0119pny BLOCK"],["Shift + \u2191 / \u2193","poprzednia / nast\u0119pna PHASE"],["Ctrl + Shift + \u2191 / \u2193","poprzedni / nast\u0119pny STEP"],["Alt + Shift + \u2191 / \u2193","poprzedni / nast\u0119pny unikalny SNAPSHOT"],["\u2191 / \u2193","poprzedni / nast\u0119pny element aktywnego poziomu"],["\u2192","zejd\u017A poziom ni\u017Cej"],["\u2190 / Esc","wr\xF3\u0107 poziom wy\u017Cej"],["Enter","wybierz element; przy SYNC ON odtw\xF3rz stan"],["F1","reset aktualnego celu Neovima/kontenera"],["F2","synchronizuj cel z kursorem drzewa UML"],["Ctrl + `","maksymalizuj pane, dopasuj siatk\u0119 i od\u015Bwie\u017C Neovima"],["Ctrl + Enter","zatwierd\u017A lub cofnij zatwierdzenie kroku"],["Alt + 0","wysu\u0144 lub schowaj panel serii i kart"],["Alt + 1","poka\u017C lub ukryj panel Neovima"],["Alt + 2","prze\u0142\u0105cz SYNC OFF / SYNC ON"],["Alt + 3","uzbr\xF3j lub rozbr\xF3j sterowanie Neovimem"],["Alt + 4","wysoki panel 90% / zapisana wysoko\u015B\u0107 normalna"],["Alt + 5","dopasuj ca\u0142y ekran Neovima; wy\u0142\u0105cz r\u0119czny suwak"],["Alt + 6","przypnij lub ukryj model alokatora"],["Alt + 7","po\u0142\u0105cz segmenty UML w szeroki spread / wr\xF3\u0107 do A4"],["Alt + 3, potem najed\u017A","przeka\u017C klawiatur\u0119 i mysz do aktywnej sesji"],["Alt + W","prefiks polece\u0144 okien Neovima ()"],["Alt + strza\u0142ka","przejd\u017A do s\u0105siedniego okna Neovima"],["F12","poka\u017C lub ukryj ten skorowidz"]].map(([r,o])=>(0,a.jsxs)("div",{children:[(0,a.jsx)("dt",{children:(0,a.jsx)("kbd",{children:r})}),(0,a.jsx)("dd",{children:o})]},r))}),(0,a.jsx)("p",{children:"Przy SYNC ON wyb\xF3r stepu odtwarza i weryfikuje przypi\u0119ty stan maszyny. SYNC OFF nie resetuje debuggera. Po najechaniu panel przekazuje do Neovima wy\u0142\u0105cznie kontrolowane zdarzenia klawiatury i myszy, wi\u0119c mo\u017Cna normalnie kontynuowa\u0107 debugowanie."})]})}):null}function yh({model:e}){let t=!!e.has_allocator_model,[n]=(0,k.useState)(hg),r=n.sources.find(E=>E.id===n.active_source)??n.sources[0],o=(0,k.useRef)(null),i=(0,k.useRef)(null),l=(0,k.useRef)(`browser-${globalThis.crypto?.randomUUID?.()??Math.random().toString(36).slice(2)}`),s=(0,k.useRef)(0),u=(0,k.useRef)(0),d=(0,k.useRef)(!1),m=(0,k.useRef)(!1),y=(0,k.useRef)(0),g=(0,k.useRef)(0),f=(0,k.useRef)(0),[S,b]=(0,k.useState)(2.18),[_,p]=(0,k.useState)(()=>localStorage.getItem("stem-card-nvim-visible")!=="false"),[c,h]=(0,k.useState)(()=>localStorage.getItem("stem-card-nvim-sync-mode")==="true"),[w,C]=(0,k.useState)(()=>localStorage.getItem("stem-card-nvim-sync-mode")==="true"&&localStorage.getItem("stem-card-nvim-control-mode")==="true"),[A,P]=(0,k.useState)(!1),[z,U]=(0,k.useState)(!1),[F,ae]=(0,k.useState)(()=>t&&localStorage.getItem("stem-card-allocator-visible")!=="false"),[me,q]=(0,k.useState)(()=>{let E=localStorage.getItem("stem-card-diagram-spread");return E===null?!!e.has_spread:E==="true"}),[V,Re]=(0,k.useState)({state:"hidden",syncMode:!1,syncReady:!1,controlMode:!1,controlEnabled:!1}),[Se,J]=(0,k.useState)(null),[at,bt]=(0,k.useState)(""),[Me,ge]=(0,k.useState)(null),[yr,se]=(0,k.useState)(null),[Zt,ue]=(0,k.useState)(null),[T,D]=(0,k.useState)(null),[G,te]=(0,k.useState)(!1),[st,xt]=(0,k.useState)(""),[ze,oe]=(0,k.useState)(()=>window.innerWidth),wt=Me==="library",kn=!!(Me||Zt),Dt=(0,k.useCallback)(E=>{if(E){ge("library"),se("library");return}ge(N=>N==="library"?null:N),se(N=>N==="library"?null:N)},[]),Q=(0,k.useCallback)(E=>{window.clearTimeout(f.current),xt(E),f.current=window.setTimeout(()=>xt(""),4200)},[]),Ae=(0,k.useCallback)(E=>{let N=n.sources.find(K=>K.id===E);if(!N?.available||!N.base_url){Q(`${N?.label??E} \xB7 \u017Ar\xF3d\u0142o niedost\u0119pne`);return}if(N.id===n.active_source)return;let I=Ep(N,n.current_resource_uuid,window.location.hash);if(!I){Q(`${N.label} \xB7 karta nie ma UUID zasobu`);return}window.location.assign(I)},[n,Q]),ut=(0,k.useCallback)(async E=>{Q(`${E} \xB7 wykonywanie\u2026`);try{let I=await fetch(E==="F1"?"/api/nvim/reset":"/api/navigation/sync",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({actor:l.current})}),K=await I.json().catch(()=>({}));if(!I.ok)throw new Error(K.message??K.error??`HTTP ${I.status}`);E==="F2"&&K.current?(u.current=Math.max(u.current,Number(K.current.revision??0)),window.dispatchEvent(new CustomEvent("stem:navigation-command",{detail:K.current})),Q(`F2 \xB7 ${String(K.current.step.number).padStart(2,"0")} ${K.current.step.label} \xB7 READY`)):Q("F1 \xB7 reset przyj\u0119ty"),window.setTimeout(()=>window.dispatchEvent(new Event("stem:nvim-refresh")),120)}catch(N){Q(`${E} \xB7 ${N instanceof Error?N.message:String(N)}`)}},[Q]),Ne=(0,k.useCallback)(async()=>{Q("Ctrl+` \xB7 przeliczanie widoku\u2026");try{let E=await fetch("/api/nvim/redraw",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({actor:l.current})}),N=await E.json().catch(()=>({}));if(!E.ok)throw new Error(N.message??N.error??`HTTP ${E.status}`);let I=N.external_ui?.grid,K=I?` \xB7 ${I.width}\xD7${I.height}`:"";Q(`Ctrl+\` \xB7 widok od\u015Bwie\u017Cony${K}`),window.dispatchEvent(new Event("stem:nvim-refresh"))}catch(E){Q(`Ctrl+\` \xB7 ${E instanceof Error?E.message:String(E)}`)}},[Q]),Ee=(0,k.useCallback)(async E=>{try{let N=await fetch("/api/viewer/state",{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({actor:l.current,viewer:E})});if(!N.ok)return;let I=await N.json();s.current=Math.max(s.current,Number(I.viewer?.revision??0))}catch{}},[]),vr=E=>{let N=Math.max(.25,Math.min(3,E));b(N),Ee({scale:N})};(0,k.useEffect)(()=>{document.documentElement.style.setProperty("--viewer-canvas-scale",S.toFixed(3))},[S]),(0,k.useEffect)(()=>{if(!me)return;let E=i.current;if(!E)return;let N=0,I=()=>{window.cancelAnimationFrame(N),N=window.requestAnimationFrame(()=>{E.scrollLeft=Math.max(0,(E.scrollWidth-E.clientWidth)/2)})},K=window.requestAnimationFrame(I);return window.addEventListener("resize",I),()=>{window.cancelAnimationFrame(K),window.cancelAnimationFrame(N),window.removeEventListener("resize",I)}},[me,S,ze]),(0,k.useEffect)(()=>{let E=()=>oe(window.innerWidth);return window.addEventListener("resize",E),()=>window.removeEventListener("resize",E)},[]),(0,k.useEffect)(()=>{document.documentElement.dataset.nvimSyncMode=c?"on":"off"},[c]),(0,k.useEffect)(()=>{document.documentElement.dataset.cardClientId=l.current},[]);let kt=E=>{p(E),E||(P(!1),U(!1)),localStorage.setItem("stem-card-nvim-visible",String(E)),Ee({nvim:{visible:E,...E?{}:{expanded:!1,fit:!1}}})},yo=E=>{let N=E&&c;C(N),localStorage.setItem("stem-card-nvim-control-mode",String(N)),Ee({nvim:{control:N}})},vo=E=>{h(E),localStorage.setItem("stem-card-nvim-sync-mode",String(E)),E||(C(!1),localStorage.setItem("stem-card-nvim-control-mode","false")),Ee({nvim:{sync:E,control:E?w:!1}})},jn=E=>{E&&kt(!0),E&&U(!1),P(E),Ee({nvim:{visible:E||_,expanded:E,fit:!1}})},ct=E=>{E&&(kt(!0),P(!1)),U(E),Ee({nvim:{visible:E||_,fit:E,expanded:!1}})},v=E=>{let N=t&&E;ae(N),localStorage.setItem("stem-card-allocator-visible",String(N)),Ee({allocator:{visible:N}})},x=E=>{let N=!!(e.has_spread&&E);q(N),localStorage.setItem("stem-card-diagram-spread",String(N))};(0,k.useEffect)(()=>{let E=0,N=window.requestAnimationFrame(()=>{E=window.requestAnimationFrame(()=>{window.dispatchEvent(new Event("stem:nvim-refresh"))})});return()=>{window.cancelAnimationFrame(N),window.cancelAnimationFrame(E)}},[A,z]),(0,k.useEffect)(()=>{fetch("/api/progress").then(E=>E.ok?E.json():Promise.reject(new Error(`HTTP ${E.status}`))).then(E=>J(E.progress)).catch(E=>bt(`Post\u0119p dzia\u0142a tylko przez serwer karty (${E.message}).`))},[]),(0,k.useEffect)(()=>{let E=!1,N=!1,I={scale:S,viewport:{width:window.innerWidth,height:window.innerHeight},nvim:{visible:_,sync:c,control:w,expanded:A,fit:z},allocator:{visible:F}},K=B=>{let he=B.nvim.visible&&B.nvim.fit,ce=B.nvim.visible&&!he&&B.nvim.expanded;b(Math.max(.25,Math.min(3,B.scale))),p(B.nvim.visible),h(B.nvim.sync),C(B.nvim.sync&&B.nvim.control),P(ce),U(he);let Z=t&&(B.allocator?.visible??!0);ae(Z),localStorage.setItem("stem-card-nvim-visible",String(B.nvim.visible)),localStorage.setItem("stem-card-nvim-sync-mode",String(B.nvim.sync)),localStorage.setItem("stem-card-nvim-control-mode",String(B.nvim.sync&&B.nvim.control)),localStorage.setItem("stem-card-allocator-visible",String(Z));let ee=++g.current;d.current=!0,window.cancelAnimationFrame(y.current),y.current=window.requestAnimationFrame(()=>{if(ee!==g.current)return;let He=Qi();Gi(He,{left:B.scroll.x,top:B.scroll.y});let Ie=Array.from(document.querySelectorAll(".paper .sheet")),St=Ie[Math.max(0,Math.min(Ie.length-1,B.scroll.page_index))]?.querySelector(".sheet-content");St&&Number.isFinite(B.scroll.sheet_scroll_top)&&Gi(St,{top:B.scroll.sheet_scroll_top??0}),y.current=window.requestAnimationFrame(()=>{ee===g.current&&(d.current=!1,m.current&&(m.current=!1,window.dispatchEvent(new Event("stem:viewer-scroll-settled"))))})})},re=async()=>{try{let B=await fetch("/api/control/state",{cache:"no-store"});if(!B.ok||E)return;let he=await B.json(),ce=he.viewer;N||(N=!0,Number(ce?.revision??0)===0&&Ee(I)),ce&&ce.revision>s.current&&(s.current=ce.revision,ce.actor!==l.current&&K(ce));let Z=he.navigation;Z&&Z.revision>u.current&&(u.current=Z.revision,Z.actor!==l.current&&(d.current&&(m.current=!0),window.dispatchEvent(new CustomEvent("stem:navigation-command",{detail:Z}))))}catch{}};re();let W=window.setInterval(re,300);return()=>{E=!0,window.clearInterval(W),g.current+=1,window.cancelAnimationFrame(y.current),d.current=!1,m.current=!1}},[]),(0,k.useEffect)(()=>{Ee({nvim:{connection:V.state,screen_cursor:V.screenCursor,grid:V.grid}})},[V.grid?.columns,V.grid?.rows,V.screenCursor?.column,V.screenCursor?.row,V.state,Ee]),(0,k.useEffect)(()=>{let E=0,N=null,I=Array.from(document.querySelectorAll(".paper .sheet")),K=I.map(W=>W.querySelector(".sheet-content")).filter(W=>!!W),re=W=>{if(d.current)return;let B=W?.currentTarget;B instanceof HTMLElement&&B.classList.contains("sheet-content")&&(N=B),window.clearTimeout(E),E=window.setTimeout(()=>{let he=N?.closest(".sheet"),ce=he?I.indexOf(he):-1,Z=ce>=0?ce:I.reduce((He,Ie,Ke)=>{let St=Ie.getBoundingClientRect(),br=Math.abs((St.top+St.bottom)/2-window.innerHeight/2);return br=0?N:I[Z]?.querySelector(".sheet-content")??null;Ee({scroll:{x:window.scrollX,y:window.scrollY,page_index:Z,sheet_scroll_top:ee?.scrollTop??0},viewport:{width:window.innerWidth,height:window.innerHeight}}),N=null},120)};return window.addEventListener("scroll",re,{passive:!0}),window.addEventListener("resize",re),window.addEventListener("stem:viewer-scroll-settled",re),K.forEach(W=>W.addEventListener("scroll",re,{passive:!0})),re(),()=>{window.clearTimeout(E),window.removeEventListener("scroll",re),window.removeEventListener("resize",re),window.removeEventListener("stem:viewer-scroll-settled",re),K.forEach(W=>W.removeEventListener("scroll",re))}},[Ee]),(0,k.useEffect)(()=>{let E=N=>{if(N.repeat)return;let I=N.altKey&&!N.ctrlKey&&!N.metaKey?N.code:"";if(I==="Digit0"&&e.library){N.preventDefault(),N.stopImmediatePropagation(),te(!1),Dt(!wt);return}if(I==="Digit1"){N.preventDefault(),N.stopImmediatePropagation(),kt(!_);return}if(I==="Digit2"){N.preventDefault(),N.stopImmediatePropagation(),vo(!c);return}if(I==="Digit3"){if(N.preventDefault(),N.stopImmediatePropagation(),!c)return;kt(!0),yo(!w);return}if(I==="Digit4"){N.preventDefault(),N.stopImmediatePropagation(),jn(!A);return}if(I==="Digit5"){N.preventDefault(),N.stopImmediatePropagation(),ct(!z);return}if(I==="Digit6"&&t){N.preventDefault(),N.stopImmediatePropagation(),v(!F);return}if(I==="Digit7"&&e.has_spread){N.preventDefault(),N.stopImmediatePropagation(),x(!me);return}if((N.key==="F1"||N.key==="F2")&&!N.altKey&&!N.ctrlKey&&!N.metaKey&&!N.shiftKey){N.preventDefault(),N.stopImmediatePropagation(),ut(N.key);return}if(N.ctrlKey&&!N.altKey&&!N.metaKey&&!N.shiftKey&&(N.code==="Backquote"||N.key==="`")){N.preventDefault(),N.stopImmediatePropagation(),Ne();return}if(N.key==="F12"){N.preventDefault(),N.stopImmediatePropagation(),Dt(!1),ge(null),se(null),ue(null),D(null),te(K=>!K);return}if(N.key==="Escape"&&(kn||G)){N.preventDefault(),N.stopImmediatePropagation(),ge(null),se(null),ue(null),D(null),te(!1);return}};return window.addEventListener("keydown",E,!0),()=>window.removeEventListener("keydown",E,!0)},[F,me,t,wt,e.has_spread,e.library,w,A,z,c,_,ut,Ne,G,kn]),(0,k.useEffect)(()=>{let E=o.current?.querySelector(".topbar");if(!E)return;let N=()=>{document.documentElement.style.setProperty("--side-panel-top",`${Math.ceil(E.getBoundingClientRect().bottom)}px`)};N();let I=new ResizeObserver(N);return I.observe(E),window.addEventListener("resize",N),()=>{I.disconnect(),window.removeEventListener("resize",N),document.documentElement.style.removeProperty("--side-panel-top")}},[]),(0,k.useEffect)(()=>()=>window.clearTimeout(f.current),[]),(0,k.useEffect)(()=>{let E=N=>{!N.altKey||N.ctrlKey||(N.preventDefault(),b(I=>{let K=Math.max(.25,Math.min(3,I*Math.exp(-Math.max(-120,Math.min(120,N.deltaY))*.001)));return Ee({scale:K}),K}))};return window.addEventListener("wheel",E,{passive:!1}),()=>window.removeEventListener("wheel",E)},[Ee]);let R=async(E,N)=>{try{let I=await fetch(`/api/progress/${encodeURIComponent(E)}`,{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({status:N})});if(!I.ok)throw new Error(await I.text());J((await I.json()).progress),bt("")}catch(I){bt(`Nie zapisano post\u0119pu: ${I instanceof Error?I.message:String(I)}`)}},M=(E,N)=>{R(E,{pending:"approved",approved:"pending"}[N])},$=0,H=e.sections.map(E=>{let N=$;return $+=E.assets.length,{section:E,figureOffset:N}}),j=[];return H.forEach(E=>{let N=E.section.spread_group??"",I=j[j.length-1];if(N&&I?.spreadGroup===N){I.pages.push(E);return}j.push({key:N||E.section.id,spreadGroup:N,spreadLabel:E.section.spread_label??"",spreadColumns:E.section.spread_columns??1,pages:[E]})}),(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)("div",{ref:o,className:`viewer-chrome${A?" is-nvim-expanded":""}${z?" is-nvim-fit":""}`,children:[(0,a.jsx)(Mg,{toc:e.toc,runtime:n,onSourceSelect:Ae,libraryAvailable:!!e.library,libraryOpen:wt,setLibraryOpen:Dt,scale:S,setScale:vr,nvimVisible:_,setNvimVisible:kt,nvimSyncMode:c,setNvimSyncMode:vo,nvimControlMode:w,setNvimControlMode:yo,nvimExpanded:A,setNvimExpanded:jn,nvimFit:z,setNvimFit:ct,allocatorVisible:F,setAllocatorVisible:v,hasAllocatorModel:t,diagramSpread:me,setDiagramSpread:x,hasDiagramSpread:!!e.has_spread,nvimSummary:V,progress:Se,progressError:at}),e.viewpoints?.length?(0,a.jsx)(Tg,{viewpoints:e.viewpoints}):null,(0,a.jsx)(Ig,{visible:t&&F,model:e}),(0,a.jsx)(Gg,{visible:_,syncMode:c,controlMode:w,expanded:A,fit:z,scale:S,onSummaryChange:Re}),st&&(0,a.jsx)("output",{className:"viewer-action-toast",children:st})]}),(0,a.jsxs)("main",{ref:i,className:`paper${me?" is-diagram-spread":""}`,children:[(0,a.jsx)(Qg,{front:e.front}),j.map(E=>{let N=Array.from({length:Math.ceil(E.pages.length/E.spreadColumns)},(B,he)=>E.pages.slice(he*E.spreadColumns,(he+1)*E.spreadColumns)),K=Math.max(1,...N.map(B=>B.reduce((he,ce)=>he+(ce.section.page_orientation==="landscape"?297:210),Math.max(0,B.length-1)*2)))*(96/25.4)*S,re=Math.min(1,Math.max(.25,(ze-24)/K)),W=E.pages.map(({section:B,figureOffset:he})=>(0,a.jsx)(lh,{section:B,progress:Se,onCycle:M,onSetStatus:R,figureOffset:he},B.id));return E.spreadGroup?(0,a.jsx)("section",{className:"sheet-spread","data-spread-group":E.spreadGroup,"aria-label":E.spreadLabel||"Wielostronicowy diagram UML",style:{"--spread-columns":E.spreadColumns,"--spread-fit-scale":re},children:W},E.key):W[0]}),e.dictionary&&(0,a.jsxs)(bs,{header:e.dictionary.header_html,footer:e.dictionary.footer_html,className:"dictionary-sheet",children:[(0,a.jsx)("h2",{children:"S\u0142ownik WE/EN/EK/KW"}),(0,a.jsx)(Ft,{html:e.dictionary.content_html})]})]}),(0,a.jsx)(bp,{side:"left",panels:[...e.library?[{id:"library",railLabel:"K",title:"Serie i karty",accent:"#167ca4",content:(0,a.jsx)(uh,{library:e.library,source:r,onNavigate:()=>Dt(!1)})}]:[],{id:"contents",railLabel:"S",title:"Spis tre\u015Bci",accent:"#16805d",content:(0,a.jsx)(ch,{toc:e.toc,onNavigate:()=>{ge(null),se(null)}})}],activeId:Me,pinnedId:yr,onActiveChange:ge,onPinnedChange:se}),(0,a.jsx)(bp,{side:"right",panels:[{id:"classroom",railLabel:"U",title:"Klasa i stanowiska",accent:"#267357",content:(0,a.jsx)(gh,{model:e})},{id:"progress",railLabel:"R",title:"Przebieg i raport",accent:"#b45b26",content:(0,a.jsx)(dh,{progress:Se,error:at})}],activeId:Zt,pinnedId:T,onActiveChange:ue,onPinnedChange:D}),(0,a.jsx)(hh,{open:G,onClose:()=>te(!1)})]})}async function vh(){let e=(0,Sp.createRoot)(document.getElementById("root"));try{let t=await fetch("./card-data.json",{cache:"no-store"});if(!t.ok)throw new Error(`Nie mo\u017Cna wczyta\u0107 card-data.json: ${t.status}`);let n=await t.json();e.render((0,a.jsx)(yh,{model:n}))}catch(t){e.render((0,a.jsxs)("main",{className:"app-load-error",children:[(0,a.jsx)("h1",{children:"Nie uda\u0142o si\u0119 uruchomi\u0107 karty"}),(0,a.jsx)("pre",{children:t instanceof Error?t.message:String(t)})]}))}}vh(); +/*! Bundled license information: + +react/cjs/react.production.min.js: + (** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +scheduler/cjs/scheduler.production.min.js: + (** + * @license React + * scheduler.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +react-dom/cjs/react-dom.production.min.js: + (** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +react/cjs/react-jsx-runtime.production.min.js: + (** + * @license React + * react-jsx-runtime.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) +*/ diff --git a/web/card-data.json b/web/card-data.json new file mode 100644 index 0000000..c820c15 --- /dev/null +++ b/web/card-data.json @@ -0,0 +1,2764 @@ +{ + "schema": "esc-card-react-view.v1", + "card": { + "id": "mpabi-inf-rv32i-asm-02-fpga-bringup", + "series": "rv32i-asm", + "series_title": "ASM · RV32I From Blinker to RISC-V", + "number": "02", + "count": "09", + "slug": "fpga-bringup", + "title": "L02 · FPGA Bring-up and Programming", + "topic": "Kontrakt płytki, symulacja, programowanie i fizyczny dowód", + "project": "From Blinker to RISC-V", + "subject": "Informatyka", + "level": "Rok 1 · RV32I", + "revision_date": "2026-07-21T00:00:00+02:00", + "status": "Gotowa do wykonania", + "version": "v00.01", + "uuid": "31e9c2ad-f597-51ab-af6a-e550c3533f32", + "author": "M. Pabiszczak", + "year": "2026" + }, + "library": { + "schema": "stem-card-library.v2", + "workspace": "edu", + "subjects": [ + { + "id": "inf", + "title": "INF · Informatyka", + "current": true, + "levels": [ + { + "id": "year-1", + "title": "Rok 1", + "current": true, + "series": [ + { + "id": "console-bash", + "title": "Bash · Console Tools", + "current": false, + "catalog_only": true, + "role": "", + "status": "", + "cards": [ + { + "id": "L01", + "title": "BusyBox — the Swiss Army Knife", + "repo": "lab-console-busybox", + "href": "http://localhost:8080", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "01", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + } + ] + }, + { + "id": "L02", + "title": "Tmux — sessions, windows and panes", + "repo": "lab-console-tmux", + "href": "http://localhost:8080", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "02", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + } + ] + }, + { + "id": "L03", + "title": "Neovim — navigation and editing", + "repo": "lab-console-neovim", + "href": "http://localhost:8080", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "03", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + } + ] + }, + { + "id": "L04", + "title": "Git Basics I — status, add and commit", + "repo": "lab-console-git-basics-1", + "href": "http://localhost:8080", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "04", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + } + ] + }, + { + "id": "L05", + "title": "Git Basics II — log, diff and restore", + "repo": "lab-console-git-basics-2", + "href": "http://localhost:8080", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "05", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + } + ] + }, + { + "id": "L06", + "title": "Git Branching I — branch, switch and merge", + "repo": "lab-console-git-branching-1", + "href": "http://localhost:8080", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "06", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + } + ] + }, + { + "id": "L07", + "title": "Git Branching II — rebase and conflicts", + "repo": "lab-console-git-branching-2", + "href": "http://localhost:8080", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "07", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + } + ] + }, + { + "id": "L08", + "title": "Docker Fundamentals", + "repo": "lab-console-docker", + "href": "http://localhost:8080", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "08", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + } + ] + } + ] + }, + { + "id": "rv32i-asm", + "title": "ASM · RV32I From Blinker to RISC-V", + "current": true, + "catalog_only": true, + "role": "", + "status": "", + "cards": [ + { + "id": "L01", + "title": "Number Systems and Storage", + "repo": "lab-rv32i-asm-number-systems", + "href": "http://localhost:8080", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "01", + "version": "v00.01", + "status": "planned", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + } + ] + }, + { + "id": "L02", + "title": "FPGA Bring-up and Programming", + "repo": "lab-rv32i-asm-fpga-bringup", + "href": "http://localhost:8080", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": true, + "number": "02", + "version": "v00.01", + "status": "planned", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + } + ] + }, + { + "id": "L03", + "title": "Blinker and Synchronous Logic", + "repo": "lab-rv32i-asm-blinker", + "href": "http://localhost:8080", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "03", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + } + ] + }, + { + "id": "L04", + "title": "The RISC-V ISA and Instruction Decoder", + "repo": "lab-rv32i-asm-decoder", + "href": "http://localhost:8080", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "04", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + } + ] + }, + { + "id": "L05", + "title": "ALU and the Verilog Assembler", + "repo": "lab-rv32i-asm-alu", + "href": "http://localhost:8080", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "05", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + } + ] + }, + { + "id": "L06", + "title": "Control Flow — Jumps and Branches", + "repo": "lab-rv32i-asm-control-flow", + "href": "http://localhost:8080", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "06", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + } + ] + }, + { + "id": "L07", + "title": "Addresses and Memory", + "repo": "lab-rv32i-asm-addresses-memory", + "href": "http://localhost:8080", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "07", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + } + ] + }, + { + "id": "L08", + "title": "Subroutines and ABI", + "repo": "lab-rv32i-asm-subroutines-abi", + "href": "http://localhost:8080", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "08", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + } + ] + }, + { + "id": "L09", + "title": "Load and Store Semantics", + "repo": "lab-rv32i-asm-load-store", + "href": "http://localhost:8080", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "09", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + } + ] + } + ] + }, + { + "id": "c", + "title": "C · Freestanding RV32I and K&R", + "current": false, + "catalog_only": true, + "role": "", + "status": "", + "cards": [ + { + "id": "C01", + "title": "ASM, C and GCC", + "repo": "lab-rv32i-strlen-asm-c-gcc", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/852fa3c7-8329-583a-a244-b639d2e04495", + "local_href": "/852fa3c7-8329-583a-a244-b639d2e04495/", + "resource_uuid": "852fa3c7-8329-583a-a244-b639d2e04495", + "available": true, + "current": false, + "number": "01", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Ręczna implementacja RV32I ASM" + }, + { + "id": "task02", + "title": "Referencyjna implementacja w C" + }, + { + "id": "task03", + "title": "ASM wyemitowany przez GCC" + } + ] + }, + { + "id": "C02", + "title": ".bss, .data and static storage", + "repo": "lab-rv32i-strlen-bss-data-stack", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/74afa921-fb51-5017-bcdb-f6e84fb444d8", + "local_href": "/74afa921-fb51-5017-bcdb-f6e84fb444d8/", + "resource_uuid": "74afa921-fb51-5017-bcdb-f6e84fb444d8", + "available": true, + "current": false, + "number": "02", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Wynik strlen w .bss" + }, + { + "id": "task02", + "title": "Wynik i napis w .data" + }, + { + "id": "task03", + "title": "Nieużyta tablica lokalna" + }, + { + "id": "task04", + "title": "Użyta tablica lokalna i stack frame" + } + ] + }, + { + "id": "C03", + "title": "ABI and Stack Frame", + "repo": "lab-rv32i-strlen-abi-stack", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/72b22a0a-3297-5d75-a6ff-d1af48132d6c", + "local_href": "/72b22a0a-3297-5d75-a6ff-d1af48132d6c/", + "resource_uuid": "72b22a0a-3297-5d75-a6ff-d1af48132d6c", + "available": true, + "current": false, + "number": "03", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Argument i wynik bez powrotu z main" + }, + { + "id": "task02", + "title": "Pełny stack frame i zachowanie ra" + } + ] + }, + { + "id": "C04", + "title": "Types, Operators and Expressions", + "repo": "lab-rv32i-c-types-operators-expressions", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/7818aa68-4983-5aca-bf7f-541cfce5c601", + "local_href": "/7818aa68-4983-5aca-bf7f-541cfce5c601/", + "resource_uuid": "7818aa68-4983-5aca-bf7f-541cfce5c601", + "available": true, + "current": false, + "number": "04", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Nazwy i stałe symboliczne" + }, + { + "id": "task02", + "title": "Typy i zakresy" + }, + { + "id": "task03", + "title": "Stałe, długość i enum" + }, + { + "id": "task04", + "title": "Deklaracje, inicjalizacja i const" + }, + { + "id": "task05", + "title": "Arytmetyka i rok przestępny" + }, + { + "id": "task06", + "title": "Porównania i logika" + }, + { + "id": "task07", + "title": "Konwersje: atoi, lower i htoi" + }, + { + "id": "task08", + "title": "Inkrementacja i operacje na napisach" + }, + { + "id": "task09", + "title": "Operatory bitowe" + }, + { + "id": "task10", + "title": "Przypisania i bitcount" + }, + { + "id": "task11", + "title": "Operator warunkowy" + }, + { + "id": "task12", + "title": "Priorytet i kolejność obliczeń" + } + ] + }, + { + "id": "C05", + "title": "Control Flow", + "repo": "lab-rv32i-c-control-flow", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/7512361d-e5e6-5c08-ab18-8be29dacdc10", + "local_href": "/7512361d-e5e6-5c08-ab18-8be29dacdc10/", + "resource_uuid": "7512361d-e5e6-5c08-ab18-8be29dacdc10", + "available": true, + "current": false, + "number": "05", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Instrukcje i bloki" + }, + { + "id": "task02", + "title": "if-else i dangling else" + }, + { + "id": "task03", + "title": "else-if i wyszukiwanie binarne" + }, + { + "id": "task04", + "title": "switch: escape i unescape" + }, + { + "id": "task05", + "title": "while, for i Shell sort" + }, + { + "id": "task06", + "title": "do-while i itoa" + }, + { + "id": "task07", + "title": "break i continue" + }, + { + "id": "task08", + "title": "goto i etykiety" + } + ] + }, + { + "id": "C06", + "title": "Functions and Program Structure", + "repo": "lab-rv32i-c-functions-program-structure", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/9667818d-b691-501d-817c-b58be4cbd594", + "local_href": "/9667818d-b691-501d-817c-b58be4cbd594/", + "resource_uuid": "9667818d-b691-501d-817c-b58be4cbd594", + "available": true, + "current": false, + "number": "06", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Funkcje i strindex" + }, + { + "id": "task02", + "title": "double i atof z wykładnikiem" + }, + { + "id": "task03", + "title": "Zewnętrzny stos RPN" + }, + { + "id": "task04", + "title": "Scope i extern" + }, + { + "id": "task05", + "title": "Nagłówki i prototypy" + }, + { + "id": "task06", + "title": "Static storage" + }, + { + "id": "task07", + "title": "Zmienne register" + }, + { + "id": "task08", + "title": "Struktura blokowa" + }, + { + "id": "task09", + "title": "Inicjalizacja" + }, + { + "id": "task10", + "title": "Rekurencja i quicksort" + }, + { + "id": "task11", + "title": "Preprocesor i makra" + } + ] + }, + { + "id": "C07", + "title": "Pointers, Arrays and Linear Allocator", + "repo": "lab-rv32i-c-pointers-arrays", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/98dbe323-c624-53d6-9fe2-4ee688c44b38", + "local_href": "/98dbe323-c624-53d6-9fe2-4ee688c44b38/", + "resource_uuid": "98dbe323-c624-53d6-9fe2-4ee688c44b38", + "available": true, + "current": false, + "number": "07", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "task01" + }, + { + "id": "task02", + "title": "task02" + }, + { + "id": "task03", + "title": "task03" + }, + { + "id": "task04", + "title": "K&R 5.4 -- Linear Memory Allocator" + }, + { + "id": "task05", + "title": "task05" + }, + { + "id": "task06", + "title": "task06" + }, + { + "id": "task07", + "title": "task07" + }, + { + "id": "task08", + "title": "task08" + }, + { + "id": "task09", + "title": "task09" + }, + { + "id": "task10", + "title": "task10" + }, + { + "id": "task11", + "title": "task11" + }, + { + "id": "task12", + "title": "task12" + } + ] + }, + { + "id": "C08", + "title": "Structures and Linked State", + "repo": "lab-rv32i-c-structures", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/f60fbba3-a788-5f1e-bc24-52af82f227c9", + "local_href": "/f60fbba3-a788-5f1e-bc24-52af82f227c9/", + "resource_uuid": "f60fbba3-a788-5f1e-bc24-52af82f227c9", + "available": true, + "current": false, + "number": "08", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Układ BlockHeader" + }, + { + "id": "task02", + "title": "Nagłówek, payload i odzyskanie właściciela" + }, + { + "id": "task03", + "title": "Intruzywna lista wolnych bloków" + }, + { + "id": "task04", + "title": "Wyrównana arena dwóch bloków" + } + ] + }, + { + "id": "C09", + "title": "smalloc, sbrk and minimal libc", + "repo": "lab-rv32i-c-smalloc-sbrk-libc", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/8e4f8bee-41f0-523b-8ea2-8926bd3d7ab0", + "local_href": "/8e4f8bee-41f0-523b-8ea2-8926bd3d7ab0/", + "resource_uuid": "8e4f8bee-41f0-523b-8ea2-8926bd3d7ab0", + "available": true, + "current": false, + "number": "09", + "version": "v00.01", + "status": "planned", + "tasks": [ + { + "id": "task01", + "title": "strlen z libc bez sterty" + }, + { + "id": "task02", + "title": "Kontrolowany brak dostawcy sbrk" + }, + { + "id": "task03", + "title": "Bounded sbrk i atomowy błąd" + }, + { + "id": "task04", + "title": "Wyrównany smalloc append-only" + }, + { + "id": "task05", + "title": "malloc/free picolibc jako czarna skrzynka" + } + ] + }, + { + "id": "C10", + "title": "RV32I Traps and Interrupt Control", + "repo": "lab-rv32i-c-interrupts", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/8fb9c110-557b-5081-8ae2-a56800f872e8", + "local_href": "/8fb9c110-557b-5081-8ae2-a56800f872e8/", + "resource_uuid": "8fb9c110-557b-5081-8ae2-a56800f872e8", + "available": true, + "current": false, + "number": "10", + "version": "v00.01", + "status": "planned", + "tasks": [ + { + "id": "task01", + "title": "ECALL i wznowienie pod mepc+4" + }, + { + "id": "task02", + "title": "Machine software IRQ i clear źródła" + }, + { + "id": "task03", + "title": "Macierz pending, MSIE i MIE" + } + ] + }, + { + "id": "C11", + "title": "Machine Timer and Periodic Work", + "repo": "lab-rv32i-c-timer", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "11", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "C12", + "title": "UART — polling and interrupts", + "repo": "lab-rv32i-c-uart", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/64f7c516-78b1-58ee-8619-a82d7061079f", + "local_href": "/64f7c516-78b1-58ee-8619-a82d7061079f/", + "resource_uuid": "64f7c516-78b1-58ee-8619-a82d7061079f", + "available": true, + "current": false, + "number": "12", + "version": "v00.01", + "status": "planned", + "tasks": [ + { + "id": "task01", + "title": "Polling jednego bajtu" + }, + { + "id": "task02", + "title": "Odbiór R przez external IRQ 16" + }, + { + "id": "task03", + "title": "Jednokomórkowa skrzynka drop-newest" + } + ] + }, + { + "id": "C13", + "title": "GPIO, Edges and Debounce", + "repo": "lab-rv32i-c-gpio", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "13", + "version": "", + "status": "planned", + "tasks": [] + } + ] + } + ] + }, + { + "id": "year-2", + "title": "Rok 2", + "current": false, + "series": [ + { + "id": "cpp", + "title": "C++", + "current": false, + "catalog_only": true, + "role": "", + "status": "", + "cards": [ + { + "id": "CPP01", + "title": "Translation Units, Linkage and Namespaces", + "repo": "lab-cpp-build-namespaces", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "01", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "CPP02", + "title": "Classes, Encapsulation and Invariants", + "repo": "lab-cpp-classes-invariants", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "02", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "CPP03", + "title": "Construction, Destruction and RAII", + "repo": "lab-cpp-lifetime-raii", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "03", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "CPP04", + "title": "References, const and Value Categories", + "repo": "lab-cpp-references-const", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "04", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "CPP05", + "title": "Copy, Move and the Rule of Zero/Five", + "repo": "lab-cpp-copy-move", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "05", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "CPP06", + "title": "Ownership and Resource Types", + "repo": "lab-cpp-ownership", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "06", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "CPP07", + "title": "Function and Class Templates", + "repo": "lab-cpp-templates", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "07", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "CPP08", + "title": "Static Polymorphism and CRTP", + "repo": "lab-cpp-static-polymorphism", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "08", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "CPP09", + "title": "Inheritance, Interfaces and Dynamic Dispatch", + "repo": "lab-cpp-dynamic-polymorphism", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "09", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "CPP10", + "title": "Operator Overloading and Strong Types", + "repo": "lab-cpp-strong-types", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "10", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "CPP11", + "title": "Containers, Iterators and Algorithms", + "repo": "lab-cpp-containers-iterators", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "11", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "CPP12", + "title": "Lambdas, Function Objects and Callables", + "repo": "lab-cpp-lambdas-callables", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "12", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "CPP13", + "title": "Error Models with and without Exceptions", + "repo": "lab-cpp-error-models", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "13", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "CPP14", + "title": "Object Layout, ABI and Binary Evidence", + "repo": "lab-cpp-abi-evidence", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "14", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "CPP15", + "title": "Resource-type Integration Project", + "repo": "lab-cpp-resource-integration", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "15", + "version": "", + "status": "planned", + "tasks": [] + } + ] + }, + { + "id": "freertos-c", + "title": "FreeRTOS C", + "current": false, + "catalog_only": true, + "role": "", + "status": "", + "cards": [ + { + "id": "FC01", + "title": "heap_4: From Linear Cursor to Reusable Blocks", + "repo": "lab-rv32i-freertos-heap4", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/aea09ab7-9bfd-5368-8d1b-c8724871c1a7", + "local_href": "/aea09ab7-9bfd-5368-8d1b-c8724871c1a7/", + "resource_uuid": "aea09ab7-9bfd-5368-8d1b-c8724871c1a7", + "available": true, + "current": false, + "number": "01", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Predict and prove reusable heap_4 blocks" + } + ] + }, + { + "id": "FC02", + "title": "First Task, TCB and Task Stack", + "repo": "lab-rv32i-freertos-c-first-task", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/a2538a1a-3c37-4c14-a6ea-db5aba8fd038", + "local_href": "/a2538a1a-3c37-4c14-a6ea-db5aba8fd038/", + "resource_uuid": "a2538a1a-3c37-4c14-a6ea-db5aba8fd038", + "available": true, + "current": false, + "number": "02", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Pierwszy task FreeRTOS — callback, context, TCB i stos" + } + ] + }, + { + "id": "FC03", + "title": "Tick, Scheduler, Priorities and Time Slicing", + "repo": "lab-rv32i-freertos-c-scheduler", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/2214a13f-34c6-40a1-a561-70a00ec98285", + "local_href": "/2214a13f-34c6-40a1-a561-70a00ec98285/", + "resource_uuid": "2214a13f-34c6-40a1-a561-70a00ec98285", + "available": true, + "current": false, + "number": "03", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Kontrolowany eksperyment schedulera FreeRTOS" + } + ] + }, + { + "id": "FC04", + "title": "Delay, Blocking and Task States", + "repo": "lab-rv32i-freertos-c-delay-state", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/4a7ef04c-ab08-5783-9192-5b02a90f05f8", + "local_href": "/4a7ef04c-ab08-5783-9192-5b02a90f05f8/", + "resource_uuid": "4a7ef04c-ab08-5783-9192-5b02a90f05f8", + "available": true, + "current": false, + "number": "04", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Delay, blokowanie, zawieszenie i self-delete" + } + ] + }, + { + "id": "FC05", + "title": "Queues and Byte-copy Semantics", + "repo": "lab-rv32i-freertos-c-queue", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/8ee8f36a-471b-51bc-97b8-e204bd541c61", + "local_href": "/8ee8f36a-471b-51bc-97b8-e204bd541c61/", + "resource_uuid": "8ee8f36a-471b-51bc-97b8-e204bd541c61", + "available": true, + "current": false, + "number": "05", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Kolejka długości 1: empty, full, copy i FIFO" + } + ] + }, + { + "id": "FC06", + "title": "Software Timers and the Daemon Task", + "repo": "lab-rv32i-freertos-c-software-timers", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/ab188300-f03d-5ddb-8316-b9534876a5bc", + "local_href": "/ab188300-f03d-5ddb-8316-b9534876a5bc/", + "resource_uuid": "ab188300-f03d-5ddb-8316-b9534876a5bc", + "available": true, + "current": false, + "number": "06", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "One-shot, auto-reload i timer daemon" + } + ] + }, + { + "id": "FC07", + "title": "Interrupt-safe API and Semaphore Handoff", + "repo": "lab-rv32i-freertos-c-isr-semaphore", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/a6da1075-8d1f-5615-b0e6-0fc33e59e1e3", + "local_href": "/a6da1075-8d1f-5615-b0e6-0fc33e59e1e3/", + "resource_uuid": "a6da1075-8d1f-5615-b0e6-0fc33e59e1e3", + "available": true, + "current": false, + "number": "07", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "External IRQ → FromISR semaphore → high-priority task" + } + ] + }, + { + "id": "FC08", + "title": "Critical Sections, Mutexes and Priority Inheritance", + "repo": "lab-rv32i-freertos-c-resource-management", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/2392f81a-ed6c-510b-86b5-06c5e2f1c6aa", + "local_href": "/2392f81a-ed6c-510b-86b5-06c5e2f1c6aa/", + "resource_uuid": "2392f81a-ed6c-510b-86b5-06c5e2f1c6aa", + "available": true, + "current": false, + "number": "08", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Low/Medium/High: ownership i dziedziczenie priorytetu" + } + ] + }, + { + "id": "FC09", + "title": "Event Groups and Multi-event Synchronization", + "repo": "lab-rv32i-freertos-c-event-groups", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/3f4c1b8b-d54b-58d5-b92d-87a7233cbd9f", + "local_href": "/3f4c1b8b-d54b-58d5-b92d-87a7233cbd9f/", + "resource_uuid": "3f4c1b8b-d54b-58d5-b92d-87a7233cbd9f", + "available": true, + "current": false, + "number": "09", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "ALL-ready i trzyosobowa bariera" + } + ] + }, + { + "id": "FC10", + "title": "Direct-to-task Notifications", + "repo": "lab-rv32i-freertos-c-notifications", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/3b57334e-2c35-5207-a51d-9321906db078", + "local_href": "/3b57334e-2c35-5207-a51d-9321906db078/", + "resource_uuid": "3b57334e-2c35-5207-a51d-9321906db078", + "available": true, + "current": false, + "number": "10", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Count, overwrite i bit flags na jednym slocie" + } + ] + }, + { + "id": "FC11", + "title": "Static Allocation and Bounded Storage", + "repo": "lab-rv32i-freertos-c-static-allocation", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/f4d7caf3-d665-5a2d-b9eb-39a120a64ff7", + "local_href": "/f4d7caf3-d665-5a2d-b9eb-39a120a64ff7/", + "resource_uuid": "f4d7caf3-d665-5a2d-b9eb-39a120a64ff7", + "available": true, + "current": false, + "number": "11", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Same behavior, two storage policies" + } + ] + }, + { + "id": "FC12", + "title": "Assertions, Hooks, Stack Checks and Runtime Diagnostics", + "repo": "lab-rv32i-freertos-c-diagnostics", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/c3027314-85a4-5e4a-af13-0939c7467a62", + "local_href": "/c3027314-85a4-5e4a-af13-0939c7467a62/", + "resource_uuid": "c3027314-85a4-5e4a-af13-0939c7467a62", + "available": true, + "current": false, + "number": "12", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Diagnose before changing the program" + } + ] + }, + { + "id": "FC13", + "title": "UART Interrupt-to-task Pipeline", + "repo": "lab-rv32i-freertos-c-uart", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/6a838fd1-ab1b-52b6-baaf-659120a4d60a", + "local_href": "/6a838fd1-ab1b-52b6-baaf-659120a4d60a/", + "resource_uuid": "6a838fd1-ab1b-52b6-baaf-659120a4d60a", + "available": true, + "current": false, + "number": "13", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Build and prove a bounded UART RX pipeline" + } + ] + }, + { + "id": "FC14", + "title": "GPIO, Hardware Timer and Deferred Work", + "repo": "lab-rv32i-freertos-c-gpio-timer", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/0cc561df-bf81-5bba-9df1-c872d5b42eb8", + "local_href": "/0cc561df-bf81-5bba-9df1-c872d5b42eb8/", + "resource_uuid": "0cc561df-bf81-5bba-9df1-c872d5b42eb8", + "available": true, + "current": false, + "number": "14", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Prove one machine-timer event from ISR to daemon GPIO work" + } + ] + }, + { + "id": "FC15", + "title": "Integrated Deterministic FreeRTOS C Application", + "repo": "lab-rv32i-freertos-c-integration", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/fe92efeb-3c38-5975-86d8-ed630e1ad3bb", + "local_href": "/fe92efeb-3c38-5975-86d8-ed630e1ad3bb/", + "resource_uuid": "fe92efeb-3c38-5975-86d8-ed630e1ad3bb", + "available": true, + "current": false, + "number": "15", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Prove one complete FreeRTOS C architecture" + } + ] + } + ] + }, + { + "id": "freertos-cpp", + "title": "FreeRTOS C++", + "current": false, + "catalog_only": true, + "role": "", + "status": "", + "cards": [ + { + "id": "K02", + "title": "C++ Task Wrapper, CRTP and Lifecycle", + "repo": "lab-rv32i-freertos-task-stack-vector", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/948ce9fd-3d0c-5721-a0ae-ef422008fec8", + "local_href": "/948ce9fd-3d0c-5721-a0ae-ef422008fec8/", + "resource_uuid": "948ce9fd-3d0c-5721-a0ae-ef422008fec8", + "available": true, + "current": false, + "number": "02", + "version": "v00.04", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "C++ Task wrapper — OOP i debugowanie cyklu życia" + } + ] + }, + { + "id": "K03", + "title": "Vector V1 — Destructor, Move, Ownership and RAII", + "repo": "lab-rv32i-freertos-vector-raii", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/de1a2818-4c30-5760-8b31-255114f82e90", + "local_href": "/de1a2818-4c30-5760-8b31-255114f82e90/", + "resource_uuid": "de1a2818-4c30-5760-8b31-255114f82e90", + "available": true, + "current": false, + "number": "03", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Vector V1 — transfer własności i powrót heapu" + } + ] + }, + { + "id": "K04", + "title": "Scheduler States, Priorities and Typed Ticks", + "repo": "lab-rv32i-freertos-scheduler-states", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/853a7dd7-b00b-4f4a-96e8-c160fc5bb04a", + "local_href": "/853a7dd7-b00b-4f4a-96e8-c160fc5bb04a/", + "resource_uuid": "853a7dd7-b00b-4f4a-96e8-c160fc5bb04a", + "available": true, + "current": false, + "number": "04", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Osiem snapshotów decyzji schedulera" + } + ] + }, + { + "id": "K05", + "title": "C++ Heap Bridge, Heap Models and HeapStats", + "repo": "lab-rv32i-freertos-heap-models", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/c456b505-dc5c-4894-b832-bb4c5afd0feb", + "local_href": "/c456b505-dc5c-4894-b832-bb4c5afd0feb/", + "resource_uuid": "c456b505-dc5c-4894-b832-bb4c5afd0feb", + "available": true, + "current": false, + "number": "05", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Fragmentacja, koalescencja i historyczny watermark" + } + ] + }, + { + "id": "K06", + "title": "MemoryResource and FreeRtosAllocator", + "repo": "lab-rv32i-freertos-allocator-resource", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/1af48a2e-8564-4e18-8115-b2c0208169d6", + "local_href": "/1af48a2e-8564-4e18-8115-b2c0208169d6/", + "resource_uuid": "1af48a2e-8564-4e18-8115-b2c0208169d6", + "available": true, + "current": false, + "number": "06", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Dwa zasoby, jeden typed allocator" + } + ] + }, + { + "id": "K07", + "title": "TaskHandle_t, Explicit Start and Static Trampoline", + "repo": "lab-rv32i-freertos-task-wrapper", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/9bffead0-4341-4350-806d-e975b1a3a08f", + "local_href": "/9bffead0-4341-4350-806d-e975b1a3a08f/", + "resource_uuid": "9bffead0-4341-4350-806d-e975b1a3a08f", + "available": true, + "current": false, + "number": "07", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Stabilny obiekt przez statyczny trampoline" + } + ] + }, + { + "id": "K08", + "title": "DynamicTask, StaticTask, Deletion and Lifetime", + "repo": "lab-rv32i-freertos-static-task", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/d9e45417-a37f-487c-813f-fc7a44d4b4f3", + "local_href": "/d9e45417-a37f-487c-813f-fc7a44d4b4f3/", + "resource_uuid": "d9e45417-a37f-487c-813f-fc7a44d4b4f3", + "available": true, + "current": false, + "number": "08", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Dwie polityki storage i pełna oś delete" + } + ] + }, + { + "id": "K09", + "title": "Scheduler Facade and Scoped Kernel Guards", + "repo": "lab-rv32i-freertos-kernel-facade", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/6a2ab068-17e7-4bfa-b726-2df37787ed69", + "local_href": "/6a2ab068-17e7-4bfa-b726-2df37787ed69/", + "resource_uuid": "6a2ab068-17e7-4bfa-b726-2df37787ed69", + "available": true, + "current": false, + "number": "09", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Fasada kernela i restore na każdej ścieżce" + } + ] + }, + { + "id": "K10", + "title": "Queue and StaticQueue", + "repo": "lab-rv32i-freertos-queue", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/66967c2d-bb34-4344-8064-f54d06cce47a", + "local_href": "/66967c2d-bb34-4344-8064-f54d06cce47a/", + "resource_uuid": "66967c2d-bb34-4344-8064-f54d06cce47a", + "available": true, + "current": false, + "number": "10", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Typed byte-copy queue z dwiema politykami storage" + } + ] + }, + { + "id": "K11", + "title": "Mutex, LockGuard and Priority Inheritance", + "repo": "lab-rv32i-freertos-mutex", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/34d7ad61-6b03-4922-8e58-b8b98316e5f7", + "local_href": "/34d7ad61-6b03-4922-8e58-b8b98316e5f7/", + "resource_uuid": "34d7ad61-6b03-4922-8e58-b8b98316e5f7", + "available": true, + "current": false, + "number": "11", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "LockGuard i zmierzone priority inheritance" + } + ] + }, + { + "id": "K12", + "title": "Semaphores and Task Notifications", + "repo": "lab-rv32i-freertos-semaphore-notify", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/fcb6107c-268f-4e86-a8cc-092e5875f6f0", + "local_href": "/fcb6107c-268f-4e86-a8cc-092e5875f6f0/", + "resource_uuid": "fcb6107c-268f-4e86-a8cc-092e5875f6f0", + "available": true, + "current": false, + "number": "12", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Semaphore slots i jednoodbiorcza notification" + } + ] + }, + { + "id": "K13", + "title": "EventGroup and Coordinated State", + "repo": "lab-rv32i-freertos-event-group", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/5fbcd7c6-3a73-4cd2-b76c-bdb016d1daaa", + "local_href": "/5fbcd7c6-3a73-4cd2-b76c-bdb016d1daaa/", + "resource_uuid": "5fbcd7c6-3a73-4cd2-b76c-bdb016d1daaa", + "available": true, + "current": false, + "number": "13", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Typed EventGroup i trzy źródła stanu" + } + ] + }, + { + "id": "K14", + "title": "Software Timer and the Timer Daemon Task", + "repo": "lab-rv32i-freertos-sw-timer", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/78db37df-63e5-4c5b-80ce-f73a411f0b50", + "local_href": "/78db37df-63e5-4c5b-80ce-f73a411f0b50/", + "resource_uuid": "78db37df-63e5-4c5b-80ce-f73a411f0b50", + "available": true, + "current": false, + "number": "14", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "Static one-shot i periodic timer na daemon tasku" + } + ] + }, + { + "id": "K15", + "title": "Explicit FromISR, UART and GPIO Wrappers", + "repo": "lab-rv32i-freertos-isr-drivers", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/02f6cd71-5c85-4883-a237-1d4ba08d9469", + "local_href": "/02f6cd71-5c85-4883-a237-1d4ba08d9469/", + "resource_uuid": "02f6cd71-5c85-4883-a237-1d4ba08d9469", + "available": true, + "current": false, + "number": "15", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "UART queue i GPIO notify z realnego IRQ" + } + ] + }, + { + "id": "K16", + "title": "Integrated Real-time Application and Evidence", + "repo": "lab-rv32i-freertos-integration", + "href": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/05b4f486-a27c-455d-a315-f4e51a4c66b3", + "local_href": "/05b4f486-a27c-455d-a315-f4e51a4c66b3/", + "resource_uuid": "05b4f486-a27c-455d-a315-f4e51a4c66b3", + "available": true, + "current": false, + "number": "16", + "version": "v00.01", + "status": "existing", + "tasks": [ + { + "id": "task01", + "title": "End-to-end RTOS service i evidence report" + } + ] + } + ] + }, + { + "id": "posix-threads", + "title": "POSIX Threads", + "current": false, + "catalog_only": true, + "role": "", + "status": "", + "cards": [ + { + "id": "PT01", + "title": "Process and Thread Execution Context", + "repo": "lab-posix-process-thread", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "01", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "PT02", + "title": "pthread_create, pthread_join and Lifecycle", + "repo": "lab-posix-pthread-create-join", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "02", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "PT03", + "title": "Arguments, Return Values and Ownership", + "repo": "lab-posix-thread-arguments", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "03", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "PT04", + "title": "Data Races and the Happens-before Question", + "repo": "lab-posix-data-races", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "04", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "PT05", + "title": "Mutex, Critical Section and Deadlock", + "repo": "lab-posix-mutex", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "05", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "PT06", + "title": "Condition Variables and Predicates", + "repo": "lab-posix-condition-variable", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "06", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "PT07", + "title": "Bounded Producer–Consumer Queue", + "repo": "lab-posix-producer-consumer", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "07", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "PT08", + "title": "POSIX Semaphores", + "repo": "lab-posix-semaphore", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "08", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "PT09", + "title": "Reader–Writer Lock", + "repo": "lab-posix-rwlock", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "09", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "PT10", + "title": "C Atomics and Memory Ordering", + "repo": "lab-posix-atomics", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "10", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "PT11", + "title": "Cancellation, Cleanup and Safe Shutdown", + "repo": "lab-posix-cancellation-cleanup", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "11", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "PT12", + "title": "Thread-local Storage", + "repo": "lab-posix-thread-local", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "12", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "PT13", + "title": "Scheduling Policy, Priority and Affinity", + "repo": "lab-posix-scheduling", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "13", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "PT14", + "title": "GDB, Sanitizers and Race Evidence", + "repo": "lab-posix-thread-debugging", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "14", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "PT15", + "title": "POSIX Threads vs FreeRTOS Integration", + "repo": "lab-posix-freertos-comparison", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "15", + "version": "", + "status": "planned", + "tasks": [] + } + ] + }, + { + "id": "oop", + "title": "OOP and Design Patterns", + "current": false, + "catalog_only": true, + "role": "reference", + "status": "", + "cards": [ + { + "id": "OOP01", + "title": "Strategy — Replaceable Behaviour", + "repo": "lab-cpp-oop-strategy", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "01", + "version": "", + "status": "design", + "tasks": [] + }, + { + "id": "OOP02", + "title": "Adapter — Stable Boundary", + "repo": "lab-cpp-oop-adapter", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "02", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP03", + "title": "Template Method — Fixed Skeleton", + "repo": "lab-cpp-oop-template-method", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "03", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP04", + "title": "Static vs Dynamic Dispatch", + "repo": "lab-cpp-oop-dispatch", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "04", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP05", + "title": "RAII and Ownership", + "repo": "lab-cpp-oop-raii", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "05", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP06", + "title": "State — Explicit Behaviour", + "repo": "lab-cpp-oop-state", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "06", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP07", + "title": "Facade — Subsystem Boundary", + "repo": "lab-cpp-oop-facade", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "07", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP08", + "title": "Command — Request as Object", + "repo": "lab-cpp-oop-command", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "08", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP09", + "title": "Observer — Subscription and Telemetry", + "repo": "lab-cpp-oop-observer", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "09", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP10", + "title": "Factory Method — Resource Policy", + "repo": "lab-cpp-oop-factory-method", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "10", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP11", + "title": "Builder — Valid Configuration", + "repo": "lab-cpp-oop-builder", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "11", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP12", + "title": "Decorator — Measurement and Diagnostics", + "repo": "lab-cpp-oop-decorator", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "12", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP13", + "title": "Composite — Component Tree", + "repo": "lab-cpp-oop-composite", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "13", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP14", + "title": "Chain of Responsibility", + "repo": "lab-cpp-oop-chain", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "14", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP15", + "title": "Pattern-language Integration", + "repo": "lab-cpp-oop-integration", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "15", + "version": "", + "status": "planned", + "tasks": [] + } + ] + } + ] + }, + { + "id": "year-3", + "title": "Rok 3 · plan", + "current": false, + "series": [ + { + "id": "modern-cpp-stl", + "title": "Modern C++ / STL", + "current": false, + "catalog_only": true, + "role": "", + "status": "scope-reserved", + "cards": [] + }, + { + "id": "std-threads", + "title": "std::thread / std::jthread", + "current": false, + "catalog_only": true, + "role": "", + "status": "scope-reserved", + "cards": [] + }, + { + "id": "boost-asio", + "title": "Boost.Asio", + "current": false, + "catalog_only": true, + "role": "", + "status": "scope-reserved", + "cards": [] + }, + { + "id": "oop", + "title": "OOP and Design Patterns", + "current": false, + "catalog_only": true, + "role": "reference", + "status": "", + "cards": [ + { + "id": "OOP01", + "title": "Strategy — Replaceable Behaviour", + "repo": "lab-cpp-oop-strategy", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "01", + "version": "", + "status": "design", + "tasks": [] + }, + { + "id": "OOP02", + "title": "Adapter — Stable Boundary", + "repo": "lab-cpp-oop-adapter", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "02", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP03", + "title": "Template Method — Fixed Skeleton", + "repo": "lab-cpp-oop-template-method", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "03", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP04", + "title": "Static vs Dynamic Dispatch", + "repo": "lab-cpp-oop-dispatch", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "04", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP05", + "title": "RAII and Ownership", + "repo": "lab-cpp-oop-raii", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "05", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP06", + "title": "State — Explicit Behaviour", + "repo": "lab-cpp-oop-state", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "06", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP07", + "title": "Facade — Subsystem Boundary", + "repo": "lab-cpp-oop-facade", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "07", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP08", + "title": "Command — Request as Object", + "repo": "lab-cpp-oop-command", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "08", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP09", + "title": "Observer — Subscription and Telemetry", + "repo": "lab-cpp-oop-observer", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "09", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP10", + "title": "Factory Method — Resource Policy", + "repo": "lab-cpp-oop-factory-method", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "10", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP11", + "title": "Builder — Valid Configuration", + "repo": "lab-cpp-oop-builder", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "11", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP12", + "title": "Decorator — Measurement and Diagnostics", + "repo": "lab-cpp-oop-decorator", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "12", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP13", + "title": "Composite — Component Tree", + "repo": "lab-cpp-oop-composite", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "13", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP14", + "title": "Chain of Responsibility", + "repo": "lab-cpp-oop-chain", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "14", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "id": "OOP15", + "title": "Pattern-language Integration", + "repo": "lab-cpp-oop-integration", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "15", + "version": "", + "status": "planned", + "tasks": [] + } + ] + } + ] + }, + { + "id": "year-4", + "title": "Rok 4 · plan", + "current": false, + "series": [ + { + "id": "transformers-gpu", + "title": "Transformers on GPU", + "current": false, + "catalog_only": true, + "role": "", + "status": "scope-reserved", + "cards": [] + } + ] + } + ] + }, + { + "id": "fiz", + "title": "FIZ · Fizyka", + "current": false, + "levels": [ + { + "id": "all", + "title": "Katalog", + "current": false, + "series": [ + { + "id": "fiz", + "title": "Fizyka", + "current": false, + "catalog_only": false, + "role": "", + "status": "", + "cards": [ + { + "id": "ruch-1d", + "title": "Ruch 1D", + "repo": "ruch-1d", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "", + "version": "", + "status": "", + "tasks": [] + }, + { + "id": "ruch-2d", + "title": "Ruch 2D", + "repo": "ruch-2d", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "", + "version": "", + "status": "", + "tasks": [] + }, + { + "id": "ruch-po-okregu", + "title": "Ruch po okręgu", + "repo": "ruch-po-okregu", + "href": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "", + "version": "", + "status": "", + "tasks": [] + } + ] + } + ] + } + ] + } + ] + }, + "total_pages": 5, + "front": { + "header_html": "
TITLEL02 · FPGA Bring-up and Programming
VER.1
DATETIME21.07.2026
PROJECTFrom Blinker to RISC-V
SERIESASM · RV32I From Blinker to RISC-V
CARD02/09
SHEET1/5
SUBJ.Inf.
PROG.
CORE
SCOPE
LEVEL
POS.
GITEA UUID31e9c2ad-f597-51ab-af6a-e550c3533f32CARD UUID31e9c2ad-f597-51ab-af6a-e550c3533f32AUTHOR  M. Pabiszczak
QRHTML wyłączony
", + "footer_html": "
UUID 31e9c2ad-f597-51ab-af6a-e550c3533f32M. PabiszczakSTRONA 1/5
", + "left_margin_html": "
TECH
", + "right_margin_html": "
OG
", + "goal_title": "Cel karty", + "goal_html": "

Uczeń przygotowuje jawny kontrakt płytki, weryfikuje top-level w symulacji i przechodzi uporządkowany preflight programowania. Automatyczny PASS nie jest dowodem działania fizycznego FPGA.

", + "scope_title": "Zakres tasków", + "scope_html": "", + "scope_columns": [ + "chapter", + "task", + "idea", + "priority", + "status", + "version" + ], + "scope_headers": [ + "Krok", + "Numer tasku", + "Najważniejsza idea", + "Priorytet", + "Status", + "Version" + ], + "scope_rows": [ + { + "chapter": "2", + "task": "Task01", + "idea_html": "

piny i active-low

", + "priority": "obowiązkowe", + "status": "ready", + "status_label": "opracowane", + "version": "v00.01", + "key": false + }, + { + "chapter": "2", + "task": "Task02", + "idea_html": "

reset i blinker RTL

", + "priority": "obowiązkowe", + "status": "ready", + "status_label": "opracowane", + "version": "v00.01", + "key": false + }, + { + "chapter": "2", + "task": "Task03", + "idea_html": "

ordered programming gate

", + "priority": "obowiązkowe", + "status": "ready", + "status_label": "opracowane", + "version": "v00.01", + "key": false + } + ] + }, + "sections": [ + { + "id": "section-1-content", + "index": 1, + "title": "Task01 · Kontrakt płytki i polaryzacja", + "show_heading": true, + "toc_entry": true, + "header_html": "
TITLEL02 · FPGA Bring-up and Programming
VER.1
DATETIME21.07.2026
PROJECTFrom Blinker to RISC-V
SERIESASM · RV32I From Blinker to RISC-V
CARD02/09
SHEET2/5
SUBJ.Inf.
PROG.
CORE
SCOPE
LEVEL
POS.
GITEA UUID31e9c2ad-f597-51ab-af6a-e550c3533f32CARD UUID31e9c2ad-f597-51ab-af6a-e550c3533f32AUTHOR  M. Pabiszczak
QRHTML wyłączony
", + "footer_html": "
UUID 31e9c2ad-f597-51ab-af6a-e550c3533f32M. PabiszczakSTRONA 2/5
", + "left_margin_html": "
TECH
", + "right_margin_html": "
OG
", + "content_html": "

Wypełnij board/board-contract.yaml dla konkretnego stanowiska. Potwierdź active-low reset, button i LED; wartości SET_* są blokadą przeciw przypadkowemu użyciu obcych pinów.

", + "tasks": [], + "assets": [ + { + "label": "fig:bringup-gate", + "logical_label": "fig:bringup-gate", + "figure_number": 1, + "caption": "Symulacja kończy automatyczny preflight; fizyczny dowód wymaga rzeczywistej płytki.", + "alt": "Diagram kolejnych bramek FPGA bring-up.", + "href": "figures/bringup-gate.svg", + "source_href": "", + "width": 0.72, + "kind": "diagram", + "interactive": null, + "tile": null, + "diagram_title": "Symulacja kończy automatyczny preflight; fizyczny dowód wymaga rzeczywistej płytki.", + "diagram_block_label": "", + "diagram_description": "", + "diagram_phase_labels": [] + } + ], + "steps": [ + { + "id": "T01-PINS", + "title": "Potwierdź pin_contract=generic i jawne polaryzacje." + } + ], + "spread_group": "", + "spread_label": "", + "spread_columns": 1, + "spread_index": 1, + "spread_count": 1, + "page_orientation": "portrait" + }, + { + "id": "section-2-content", + "index": 2, + "title": "Task02 · Top-level blinker", + "show_heading": true, + "toc_entry": true, + "header_html": "
TITLEL02 · FPGA Bring-up and Programming
VER.1
DATETIME21.07.2026
PROJECTFrom Blinker to RISC-V
SERIESASM · RV32I From Blinker to RISC-V
CARD02/09
SHEET3/5
SUBJ.Inf.
PROG.
CORE
SCOPE
LEVEL
POS.
GITEA UUID31e9c2ad-f597-51ab-af6a-e550c3533f32CARD UUID31e9c2ad-f597-51ab-af6a-e550c3533f32AUTHOR  M. Pabiszczak
QRHTML wyłączony
", + "footer_html": "
UUID 31e9c2ad-f597-51ab-af6a-e550c3533f32M. PabiszczakSTRONA 3/5
", + "left_margin_html": "
TECH
", + "right_margin_html": "
OG
", + "content_html": "

Zasymuluj znany reset i dzielnik. LED musi zmienić stan w taktach 4, 8, 12 i 16. VCD dowodzi zachowania RTL, ale nie poprawności constraintów ani połączeń płytki.

", + "tasks": [], + "assets": [], + "steps": [ + { + "id": "T02-BLINK", + "title": "Zmierz reset_known=1, cztery krawędzie i zapisz VCD." + } + ], + "spread_group": "", + "spread_label": "", + "spread_columns": 1, + "spread_index": 1, + "spread_count": 1, + "page_orientation": "portrait" + }, + { + "id": "section-3-content", + "index": 3, + "title": "Task03 · Bramka programowania", + "show_heading": true, + "toc_entry": true, + "header_html": "
TITLEL02 · FPGA Bring-up and Programming
VER.1
DATETIME21.07.2026
PROJECTFrom Blinker to RISC-V
SERIESASM · RV32I From Blinker to RISC-V
CARD02/09
SHEET4/5
SUBJ.Inf.
PROG.
CORE
SCOPE
LEVEL
POS.
GITEA UUID31e9c2ad-f597-51ab-af6a-e550c3533f32CARD UUID31e9c2ad-f597-51ab-af6a-e550c3533f32AUTHOR  M. Pabiszczak
QRHTML wyłączony
", + "footer_html": "
UUID 31e9c2ad-f597-51ab-af6a-e550c3533f32M. PabiszczakSTRONA 4/5
", + "left_margin_html": "
TECH
", + "right_margin_html": "
OG
", + "content_html": "

Przejdź kolejno przez cable, device ID, configure i observe. Model blokuje pominięcie etapu, lecz jego PASS zawsze kończy się hardware_observed=0 oraz claim=preflight-only. Fizyczny wynik wpisz osobno.

", + "tasks": [], + "assets": [], + "steps": [ + { + "id": "T03-GATE", + "title": "Potwierdź kolejność i brak fałszywego hardware claim." + } + ], + "spread_group": "", + "spread_label": "", + "spread_columns": 1, + "spread_index": 1, + "spread_count": 1, + "page_orientation": "portrait" + } + ], + "viewpoints": [], + "has_spread": false, + "has_allocator_model": false, + "dictionary": { + "header_html": "
TITLEL02 · FPGA Bring-up and Programming
VER.1
DATETIME21.07.2026
PROJECTFrom Blinker to RISC-V
SERIESASM · RV32I From Blinker to RISC-V
CARD02/09
SHEET5/5
SUBJ.Inf.
PROG.
CORE
SCOPE
LEVEL
POS.
GITEA UUID31e9c2ad-f597-51ab-af6a-e550c3533f32CARD UUID31e9c2ad-f597-51ab-af6a-e550c3533f32AUTHOR  M. Pabiszczak
QRHTML wyłączony
", + "footer_html": "
UUID 31e9c2ad-f597-51ab-af6a-e550c3533f32M. PabiszczakSTRONA 5/5
", + "content_html": "
WE 01 Board to observation

Bezpieczny i audytowalny FPGA bring-up.

OG

EN IP RV02.01 Uczeń ocenia siłę dowodów bring-up.
  • KW IP RV02.01 Rozróżnia VCD od obserwacji fizycznej.

TECH

EK INF04 RV02.01 Uczeń przygotowuje kontrakt i kolejność programowania.
  • KW INF04 RV02.01 Nie omija identyfikacji urządzenia.
" + }, + "toc": [ + { + "id": "section-1-content", + "label": "1. Task01 · Kontrakt płytki i polaryzacja" + }, + { + "id": "section-2-content", + "label": "2. Task02 · Top-level blinker" + }, + { + "id": "section-3-content", + "label": "3. Task03 · Bramka programowania" + } + ] +} diff --git a/web/figures/bringup-gate.svg b/web/figures/bringup-gate.svg new file mode 100644 index 0000000..81204e7 --- /dev/null +++ b/web/figures/bringup-gate.svg @@ -0,0 +1,13 @@ + + Drabinka FPGA bring-upKolejne bramki to kontrakt płytki, symulacja, bitstream, identyfikacja urządzenia, konfiguracja i fizyczna obserwacja LED. + + + CONTRACTboard + pins + SIMRTL + VCD + BUILDbitstream + IDENTIFYdevice ID + CONFIGprogrammer + LEDobserve + AUTOMATED PASScontract + sim + preflight model + PHYSICAL EVIDENCE REQUIREDreal board, digest, device ID, log, observation + diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..8f9f9f5 --- /dev/null +++ b/web/index.html @@ -0,0 +1,15 @@ + + + + + + + Karta pracy + + + + +
+ + + diff --git a/web/style.css b/web/style.css new file mode 100644 index 0000000..5ed9298 --- /dev/null +++ b/web/style.css @@ -0,0 +1,42 @@ + +:root{--ink:#111;--muted:#666;--line:#b9b9b9;--line-soft:#dddddd;--paper:#fff;--desk:#eeeeee;--og:#008000;--tech:#d36b00;--kw:#0018c8;--we:#e00000;--viewer-canvas-scale:1;--viewer-content-scale:1} +*{box-sizing:border-box} html{scroll-behavior:smooth} body{margin:0;background:var(--desk);color:var(--ink);font-family:"Latin Modern Roman","LM Roman 12",Georgia,"Times New Roman",serif;line-height:1.25} +.topbar{position:sticky;top:0;z-index:20;background:rgba(245,245,245,.94);border-bottom:1px solid #d0d0d0;padding:3px 0;font-family:system-ui,-apple-system,Segoe UI,sans-serif} +.topbar-inner{width:calc(100% - 8px);max-width:none;margin:0 4px;display:flex;align-items:center;justify-content:space-between;gap:6px}.topbar details{min-width:0;flex:1}.topbar summary{cursor:pointer;font-size:16px;line-height:1.35;font-weight:650}.toc-links{display:flex;flex-wrap:wrap;gap:6px;margin-top:5px}.toc-links a{font-size:16px;color:#333;text-decoration:none;border:1px solid #ccc;background:white;border-radius:3px;padding:2px 5px}.viewer-zoom-controls{display:flex;align-items:center;gap:7px;flex:none}.viewer-zoom-status{font:600 16px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;color:#444;white-space:nowrap}.viewer-zoom-reset{border:1px solid #bbb;border-radius:3px;background:#fff;color:#333;padding:1px 6px;font:600 16px/1.35 system-ui,-apple-system,Segoe UI,sans-serif;cursor:pointer}.viewer-zoom-reset:hover{background:#eee}.viewer-zoom-reset:focus-visible{outline:2px solid #555;outline-offset:1px} +.paper{padding:18px 12px 44px;overflow-x:auto}.sheet{position:relative;width:210mm;min-height:297mm;margin:0 auto 18px;background:var(--paper);box-shadow:0 2px 12px rgba(0,0,0,.13);display:block;padding:0;overflow:visible;zoom:var(--viewer-canvas-scale)}.sheet-content{position:relative;width:100%;min-height:297mm;padding:13mm 21.5mm 15mm;transform:scale(var(--sheet-effective-scale,1));transform-origin:top left}.resource-header-enabled .sheet-content{padding-top:5mm}.sheet-content>.page-resource-header{margin-bottom:3mm} +@media(max-width:900px){.sheet{height:auto}}@media print{.sheet{height:auto}} +.pdf-main{min-width:0;width:100%}.running-header{display:flex;justify-content:space-between;gap:18px;align-items:flex-end;border-bottom:1px solid #111;padding-bottom:3px;margin-bottom:26px;font-size:15px}.running-header span{white-space:nowrap} +.hero h1{font-size:24px;line-height:1.15;margin:0 0 4px;border-bottom:1px solid #111;padding-bottom:4px}.hero .byline{float:right;margin-top:-31px;font-size:15px} +.meta{clear:both;display:grid;grid-template-columns:148px minmax(0,1fr);gap:4px 18px;padding:34px 0 24px;border-bottom:1px solid #111;font-size:17px}.meta dt{color:#333}.meta dd{margin:0;font-family:"Latin Modern Mono",ui-monospace,SFMono-Regular,Consolas,monospace} +.front-scope{padding:0 0 3mm;border-bottom:1px solid var(--line-soft)}.front-scope h2{font-size:24px;line-height:1.15;margin:0 0 3mm}.front-scope p{font-size:16px} +.scope-table-wrap{margin:3mm 0;overflow:hidden}.scope-table{width:100%;border-collapse:collapse;font-family:"Latin Modern Mono",ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px;line-height:1.22}.scope-table th,.scope-table td{border:0;border-bottom:1px solid #888;padding:4px 7px;text-align:left;vertical-align:top}.scope-table th{border-bottom:2px solid #555;background:#f3f3f3;font-weight:700}.scope-table th:first-child,.scope-table td:first-child{width:7%}.scope-table--task th:nth-child(2),.scope-table--task td:nth-child(2){width:11%}.scope-table--task th:nth-child(4),.scope-table--task td:nth-child(4){width:19%}.scope-table--task th:nth-child(5),.scope-table--task td:nth-child(5){width:14%}.scope-table--task th:nth-child(6),.scope-table--task td:nth-child(6){width:10%}.scope-table tr.scope-row--key td{font-weight:700;background:#f4f4f4;border-top:2px solid #111;border-bottom:2px solid #111}.scope-table tr.scope-row--key td:first-child{border-left:2px solid #111}.scope-table tr.scope-row--key td:last-child{border-right:2px solid #111}.scope-table td p{font:inherit;margin:0}.scope-table code{font-size:inherit}.scope-status{display:inline-block;white-space:nowrap;border:1px solid #aaa;border-radius:2px;padding:1px 4px;font-weight:600;background:#f5f5f5}.scope-status[data-status="in-progress"]{border-color:#307da1;background:#eaf5fa;color:#174f69}.scope-status[data-status="ready"]{border-color:#4c7d56;background:#edf6ee;color:#295b33}.scope-status[data-status="draft"],.scope-status[data-status="review"]{border-color:#9a7932;background:#faf4e5;color:#694f16}.scope-status[data-status="blocked"]{border-color:#9c4b4b;background:#faeded;color:#712f2f} +.card-section h2{font-size:24px;line-height:1.15;margin:0 0 4mm}.section-heading{display:flex;align-items:flex-start;gap:0}.section-heading .section-index{font-family:"Latin Modern Mono",ui-monospace,SFMono-Regular,Consolas,monospace;font-size:23px;margin-right:12mm;font-weight:400}.section-heading .section-title{min-width:0}.task-identity{margin-left:auto;padding-left:22px;text-align:right;font-family:"Latin Modern Mono",ui-monospace,SFMono-Regular,Consolas,monospace;font-size:10px;line-height:1.25;color:#555;white-space:nowrap}.task-identity strong,.task-identity-name,.task-identity code{display:block}.task-identity strong{font-size:12px;color:#111}.task-identity-name{max-width:240px;overflow:hidden;text-overflow:ellipsis}.task-identity code{font-size:9px;color:#777} +p{font-size:16px;margin:0 0 3mm}.pdf-main ul{font-size:16px;margin:2mm 0 3mm 7mm;padding:0}.pdf-main li{margin:1.2mm 0} +.pdf-margin{position:absolute;top:34mm;width:18mm;font-family:"Latin Modern Mono",ui-monospace,SFMono-Regular,Consolas,monospace;font-size:5px;line-height:1.12;color:#333;max-height:245mm;overflow:hidden}.pdf-margin.left{left:2mm;text-align:left}.pdf-margin.right{right:2mm;text-align:left} +.margin-title{font-weight:700;color:#666;margin-bottom:3px;letter-spacing:.02em}.margin-list{display:flex;flex-direction:column;align-items:flex-start}.margin-tag{display:block;margin:0 0 1px;padding:0;border:0;background:transparent;font:inherit;line-height:inherit;text-align:left;cursor:pointer;margin-left:var(--indent)}.margin-tag.we{color:var(--we)}.margin-tag.og,.margin-tag.tech{color:var(--og)}.margin-tag.kw{color:var(--kw)}.margin-tag .local{color:#777}.margin-tag:hover{text-decoration:underline}.margin-empty{color:#aaa} +.step-map{font-family:"Latin Modern Mono",ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px;line-height:1.3;color:#5c5c5c;background:transparent;border:0;border-top:1px solid #cfcfcf;border-bottom:1px solid #e0e0e0;border-radius:0;padding:8px 0;margin:0 0 16px}.step-map summary{cursor:pointer;font-weight:400;color:#555} +.tree-legend{display:flex;flex-wrap:wrap;gap:4px;margin:7px 0}.steps{display:grid;gap:6px}.step-row{border-top:1px dashed #c9c9c9;padding-top:6px}.step-row:first-child{border-top:0}.step-title span{font-weight:700;margin-right:7px}.step-trees{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}.step-refs{display:flex;flex-wrap:wrap;gap:6px;margin-top:3px}.step-refs a{font-size:10px;color:#666} +.tree-token{font:10px/1 "Latin Modern Mono",ui-monospace,SFMono-Regular,Consolas,monospace;border:1px solid #c8c8c8;background:#fff;border-radius:3px;padding:3px 5px;cursor:pointer}.tree-token.active{background:#111;color:white;border-color:#111} +.equation{display:grid;grid-template-columns:1fr auto;align-items:center;gap:14px;margin:18px 0;padding:4px 0;background:transparent;border:0}.equation .display{font-size:17px}.equation-number{font-family:"Latin Modern Mono",ui-monospace,SFMono-Regular,Consolas,monospace;font-size:15px} +figure{margin:24px 0;text-align:center}figure img{max-width:100%;display:block;margin:0 auto;border:0;border-radius:0}figcaption{font-size:14px;color:#444;margin-top:8px;text-align:left}.card-asset.asset-screenshot img{border:1px solid #aaa}.card-asset a{display:block;cursor:zoom-in} +.table-wrap{overflow:auto;margin:14px 0}table{border-collapse:collapse;width:100%;font-size:16px}th,td{border:1px solid #111;padding:5px 8px;text-align:left;vertical-align:top}th{font-weight:400;background:white} +.empty-check{display:inline-block;width:1em;height:1em;border:1px solid #111;vertical-align:-.12em}.answer-line,.dotfill{display:block;border-bottom:1px dotted #111;height:1.45em;min-width:10em}.write-row{display:block;min-height:2.8em} +code{font-family:"Latin Modern Mono",ui-monospace,SFMono-Regular,Consolas,monospace}.tasks{font-size:16px}.task-legacy{margin:2.5mm 0}.task-legacy>strong{display:block;font:700 10px/1.2 "Latin Modern Mono",ui-monospace,monospace;color:#555}.task-flow{border:1px solid #222;margin:1mm 0 2mm;background:#fff}.task-flow-header{display:grid;grid-template-columns:auto 1fr auto;align-items:baseline;gap:6px;padding:5px 7px;background:#eaeaea;border-bottom:1px solid #222}.task-flow-header span{font:700 10px/1.2 "Latin Modern Mono",ui-monospace,monospace;color:#222}.task-flow-header h3{font-size:18px;margin:0}.task-flow-header code{font-size:8px;color:#555}.task-block{margin:6px 7px;padding:5px 6px;border:1px solid #777;background:#fff}.task-block header,.task-exercise header{display:flex;align-items:baseline;gap:5px;margin-bottom:3px}.task-block header span,.task-exercise header span{font:700 9px/1.2 "Latin Modern Mono",ui-monospace,monospace;text-transform:uppercase;color:#555}.task-block h4,.task-exercise h4{font-size:16px;margin:0}.block-steps{margin:4px 0 0 16px!important;padding:0;display:grid;gap:3px}.block-steps li{margin:0;border:1px solid #b9b9b9;padding:4px 5px;background:#fff}.block-steps li::marker{font-family:"Latin Modern Mono",ui-monospace,monospace;font-weight:700}.block-steps li p:last-child{margin-bottom:0}.task-exercise{margin:6px 7px;border:1px solid #777;border-left:1px solid #222;padding:5px 6px;background:#fafafa}.task-exercise header{flex-wrap:wrap}.exercise-based-on{margin-left:auto;font-size:8px;color:#777}.exercise-evidence{border-top:1px dashed #bbb;margin-top:5px;padding-top:4px}.exercise-evidence>strong{font-size:11px;text-transform:uppercase}.exercise-criterion{font-size:13px;margin:4px 0 0}.task-flow>footer{padding:5px 7px;border-top:1px solid #222;font-size:12px;background:#f6f6f6}.task-conclusion{margin:8px 0;padding:7px 9px;border:1px solid #315f78;background:#f3f9fc}.task-conclusion>strong{display:block;margin-bottom:3px;font:700 10px/1.2 "Latin Modern Mono",ui-monospace,monospace;color:#315f78}.task-conclusion p{margin:0}.we-block{border-top:1px solid #ddd;padding:8px 0}.we-block summary{cursor:pointer}.dictionary-sheet .pdf-margin{display:none} +.inspector{position:fixed;right:14px;bottom:14px;width:min(360px,calc(100vw - 28px));max-height:46vh;overflow:auto;background:white;border:1px solid #cfcfcf;box-shadow:0 4px 18px rgba(0,0,0,.18);border-radius:6px;padding:12px;font-family:system-ui,-apple-system,Segoe UI,sans-serif;font-size:13px;z-index:30}.inspector h2{font-size:14px;margin:0 0 8px}.inspector .empty{color:#777}.tree-card h3{font-size:15px;margin:6px 0}.tree-card code{display:inline-block;margin:4px 0}.tree-card .line{font-weight:700}.tree-card .line.OG{color:var(--og)}.tree-card .line.TECH{color:var(--tech)}.kw-list{padding-left:18px}.kw-list code{color:var(--kw)} +@media(max-width:900px){.sheet{display:block;min-height:0}.sheet-content{min-height:0;padding:26px 18px}.pdf-margin{position:static;max-height:none;overflow:visible;margin:10px 0;padding:8px;border:1px solid #ddd}.pdf-margin.left{border-left:3px solid var(--tech)}.pdf-margin.right{border-left:3px solid var(--og)}.hero .byline{float:none;margin:0 0 14px}.meta{grid-template-columns:1fr}.section-heading{flex-wrap:wrap}.task-identity{width:100%;padding:6px 0 0}.inspector{position:static;width:auto;max-height:none;margin:0 12px 18px}.topbar{position:sticky;top:0}.viewer-zoom-status{font-size:16px}} +@media print{html,body{background:white}.topbar,.inspector{display:none}.paper{padding:0}.sheet{position:relative;display:block;width:auto;min-height:0;margin:0;box-shadow:none;break-after:page;padding:0;overflow:visible;zoom:1!important}.sheet-content{position:relative;width:auto;min-height:0;padding:0;transform:none!important}.sheet:last-child{break-after:auto}.resource-header-enabled .sheet-content{padding:0}.sheet-content>.page-resource-header,.sheet-content>.page-resource-footer{display:none!important}.pdf-margin{position:absolute;top:0;width:18mm;max-height:none;overflow:hidden;margin:0;padding:0;border:0}.pdf-margin.left{left:-19.5mm}.pdf-margin.right{right:-19.5mm}.task-block,.task-exercise,.block-steps li,figure{break-inside:avoid}} + +.page-resource-header{width:100%;height:28mm;min-height:28mm;max-height:28mm;background:#fff;color:#111;overflow:hidden;font-family:ui-monospace,SFMono-Regular,"Latin Modern Mono",Consolas,monospace;line-height:1.08} +.resource-header-table{width:100%;height:100%;border-collapse:collapse;table-layout:fixed;border:1px solid #111}.resource-header-table>tbody>tr{height:100%}.resource-header-center{height:100%;padding:0;vertical-align:middle;overflow:hidden}.resource-header-grid{display:grid;grid-template-rows:3fr 3fr 3fr 2fr 2fr;width:100%;height:100%}.resource-header-row{display:grid;min-width:0;min-height:0}.resource-header-field{display:flex;flex-direction:column;align-items:stretch;justify-content:space-between;gap:.08mm;min-width:0;padding:.28mm .55mm;border-right:1px solid #888;border-bottom:1px solid #aaa;overflow:hidden;white-space:nowrap}.resource-header-row .resource-header-field:last-child{border-right:0}.resource-header-row:last-child .resource-header-field{border-bottom:0}.resource-header-field a{display:flex;flex-direction:column;align-items:flex-start;gap:.08mm;min-width:0;color:#111;text-decoration:none}.resource-label{align-self:flex-start;font:800 3.2pt/1 ui-monospace,SFMono-Regular,"Latin Modern Mono",Consolas,monospace;text-transform:uppercase;flex:none}.resource-value{align-self:flex-end;max-width:100%;text-align:right;font:700 6.4pt/1 ui-monospace,SFMono-Regular,"Latin Modern Mono",Consolas,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.resource-header-field--title .resource-value{font-size:7.7pt;font-weight:800}.resource-header-field--emphasis .resource-value{font-size:6.6pt;font-weight:800}.resource-header-field--compact{gap:.05mm;padding:.2mm .2mm}.resource-header-field--compact .resource-label{font-size:2.85pt}.resource-header-field--compact .resource-value{font-size:4.45pt}.resource-header-field--identity{display:flex;flex-direction:column;align-items:stretch;justify-content:center;gap:.15mm;padding:.12mm .45mm;font:700 3.55pt/1.1 ui-monospace,SFMono-Regular,"Latin Modern Mono",Consolas,monospace}.resource-header-field--identity>span{display:flex;align-items:baseline;gap:.3mm;min-width:0}.resource-header-field--identity b{font-weight:800;flex:none}.resource-header-field--identity .resource-identity-value{margin-left:auto;text-align:right}.resource-header-field--identity .resource-author{margin-left:auto;text-align:right}.resource-header-field--url-row{display:flex;align-items:stretch;justify-content:center;padding:.2mm .55mm}.resource-header-field--url-row a{display:block;overflow:hidden;text-overflow:clip;font:700 4.9pt/1.05 ui-monospace,SFMono-Regular,"Latin Modern Mono",Consolas,monospace}.resource-header-field--url-row b{font-weight:800} +.resource-qr-cell{height:100%;padding:0;vertical-align:middle;overflow:hidden}.resource-qr-cell--left{border-right:1px solid #777}.resource-qr-cell--right{border-left:1px solid #777}.resource-qr{box-sizing:border-box;width:100%;height:100%;display:flex;align-items:center;justify-content:center;padding:.4mm;color:#333;text-decoration:none}.resource-qr .title-qr{display:block;width:22mm;height:22mm;max-width:22mm;max-height:22mm;flex:none}.resource-qr-empty{text-align:center}.resource-qr-empty span,.resource-qr-empty strong{display:block;font:600 5pt/1.15 ui-monospace,SFMono-Regular,Consolas,monospace} +.page-resource-footer{position:absolute;right:21.5mm;bottom:3mm;left:21.5mm;display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);align-items:center;gap:8px;min-height:5mm;border-top:.35mm solid #252525;padding-top:1.2mm;color:#68747a;font:650 7px/1.15 ui-monospace,SFMono-Regular,Consolas,monospace}.page-resource-footer__uuid{overflow:hidden;color:#4d5d65;text-align:left;text-overflow:ellipsis;white-space:nowrap;font:inherit}.page-resource-footer__author{color:#4d5d65;text-align:center;white-space:nowrap}.page-resource-footer__page{color:#283940;text-align:right;white-space:nowrap} + +@page{ + size:A4; + margin:33mm 21.5mm 10mm; + @top-left{content:" TITLE VER. DATETIME \A \A L02 · FPGA Bring-up and Programming 1 21.07.2026 \A PROJECT SERIES CARD SHEET \A \A From Blinker to RISC-V ASM · RV32I From Blinker to RISC-V 02/09 " counter(page) "/" counter(pages) " \A SUBJ. PROG. CORE SCOPE LEVEL POS. GITEA UUID 31e9c2ad-f597-51ab-af6a-e550c3533f32\A CARD UUID 31e9c2ad-f597-51ab-af6a-e550c3533f32 AUTHOR M. Pabiszczak\A Inf. — — — — — \A \A GITEA https://gitea.local/edu-inf/lab-rv32i-asm-fpga-bringup \A \A HTML http://localhost:8080 ";width:143mm;height:28mm;box-sizing:border-box;padding:.2mm .7mm 0;border-block:1px solid #111;border-left:1px solid #111;background-color:#fff;background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMTkgMjgiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPjxyZWN0IHdpZHRoPSIxMTkiIGhlaWdodD0iMjgiIGZpbGw9IndoaXRlIi8+PHBhdGggZD0iTTAgNi40NjE1NEgxMTkgTTAgMTIuOTIzMUgxMTkgTTAgMTkuMzg0NkgxMTkgTTAgMjMuNjkyM0gxMTkgTTU5LjUgMFY2LjQ2MTU0IE03Ny41IDBWNi40NjE1NCBNNTkuNSA2LjQ2MTU0VjEyLjkyMzEgTTg5LjUgNi40NjE1NFYxMi45MjMxIE0xMDYuNSA2LjQ2MTU0VjEyLjkyMzEgTTcgMTIuOTIzMVYxOS4zODQ2IE0xMyAxMi45MjMxVjE5LjM4NDYgTTMwLjUgMTIuOTIzMVYxOS4zODQ2IE00MS41IDEyLjkyMzFWMTkuMzg0NiBNNTAuNSAxMi45MjMxVjE5LjM4NDYgTTU5LjUgMTIuOTIzMVYxOS4zODQ2IiBmaWxsPSJub25lIiBzdHJva2U9IiM4ODgiIHN0cm9rZS13aWR0aD0iMC4xNCIvPjwvc3ZnPg==");background-repeat:no-repeat;background-position:center;background-size:100% 100%;white-space:pre;vertical-align:top;text-align:left;font:700 5.2px/2.12308mm ui-monospace,SFMono-Regular,Consolas,monospace;letter-spacing:-.02px;color:#111;overflow:hidden} + @top-right{content:"";width:24mm;height:28mm;box-sizing:border-box;padding:0;border:1px solid #111;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:22mm 22mm;vertical-align:middle;text-align:center;font:0/0 sans-serif;color:transparent;background-image:none} + @bottom-left{content:"UUID 31e9c2ad-f597-51ab-af6a-e550c3533f32";border-top:.35mm solid #252525;padding-top:1mm;color:#68747a;font:650 7px/1.15 ui-monospace,SFMono-Regular,Consolas,monospace} + @bottom-center{content:"M. Pabiszczak";border-top:.35mm solid #252525;padding-top:1mm;color:#4d5d65;font:650 7px/1.15 ui-monospace,SFMono-Regular,Consolas,monospace} + @bottom-right{content:"STRONA " counter(page) "/" counter(pages);border-top:.35mm solid #252525;padding-top:1mm;color:#283940;font:650 7px/1.15 ui-monospace,SFMono-Regular,Consolas,monospace} +}