From 6c9e07abdf170861329d37845bb464e0866bbff9 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 21 Jul 2026 18:58:00 +0200 Subject: [PATCH] feat(L01): add number systems and storage card --- .gitea/workflows/render-pdf.yml | 63 + .gitignore | 30 + Makefile | 19 + README.md | 23 + answers/number-storage-evidence.md | 11 + doc/assets/number-storage.png | Bin 0 -> 104355 bytes doc/assets/number-storage.svg | 10 + 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_positional_encoding.sv | 12 + src/tasks/task02_extension_shifts.sv | 13 + src/tasks/task03_little_endian_memory.sv | 21 + stem-card.yaml | 10 + tests/card_contract.test.mjs | 5 + tests/card_state_db.test.mjs | 67 + tests/reference_model.py | 36 + tests/task01_positional_encoding_tb.sv | 10 + tests/task02_extension_shifts_tb.sv | 10 + tests/task03_little_endian_memory_tb.sv | 11 + tests/test_rtl.sh | 13 + tools/card-action.sh | 14 + web/app.css | 1 + web/app.js | 61 + web/card-data.json | 2751 ++++++++++++++++++++++ web/figures/number-storage.svg | 10 + web/index.html | 15 + web/style.css | 42 + 42 files changed, 7303 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/number-storage-evidence.md create mode 100644 doc/assets/number-storage.png create mode 100644 doc/assets/number-storage.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_positional_encoding.sv create mode 100644 src/tasks/task02_extension_shifts.sv create mode 100644 src/tasks/task03_little_endian_memory.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_positional_encoding_tb.sv create mode 100644 tests/task02_extension_shifts_tb.sv create mode 100644 tests/task03_little_endian_memory_tb.sv create mode 100755 tests/test_rtl.sh 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/number-storage.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..094db8c --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +VERILATOR ?= verilator +TASK ?= task01_positional_encoding +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..a068024 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# L01 · Number Systems and Storage + +Pierwsza karta serii **ASM · RV32I From Blinker to RISC-V** buduje język, +który będzie używany przy instrukcjach, natychmiastowych argumentach i pamięci. +Uczeń przechodzi od wag bitów przez rozszerzenia i przesunięcia do zapisu słowa +RV32I w pamięci little-endian. + +| Task | Mierzalny dowód | +| --- | --- | +| `task01_positional_encoding` | `173 = 10101101₂ = AD₁₆` i suma wag 173 | +| `task02_extension_shifts` | zero/sign extension oraz logiczne/arytmetyczne `>> 1` dla `0x80` | +| `task03_little_endian_memory` | `0x12345678` zapisane jako bajty `78,56,34,12` i poprawny roundtrip | + +```bash +stemctl card use rv32i-asm L01 +stemctl test native-amd64 rv32i-asm L01 1 +stemctl test hazard3-sim rv32i-asm L01 2 +stemctl debug hazard3-sim rv32i-asm L01 3 +``` + +Profil `native-amd64` uruchamia niezależny model referencyjny. Profil +`hazard3-sim` wykonuje self-checking SystemVerilog i w akcji `debug` zapisuje +VCD. Karta nie wymaga płytki FPGA. diff --git a/answers/number-storage-evidence.md b/answers/number-storage-evidence.md new file mode 100644 index 0000000..653bfe8 --- /dev/null +++ b/answers/number-storage-evidence.md @@ -0,0 +1,11 @@ +# Evidence · L01 Number Systems and Storage + +Przed testem wpisz przewidywanie, potem zestaw je z komunikatem PASS i VCD. + +| Task | Przewidywanie | Pomiar | Wyjaśnienie | +| --- | --- | --- | --- | +| 01 | `173 = ...₂ = ...₁₆` | | | +| 02 | `zext/sext/lsr/asr` dla `0x80` | | | +| 03 | bajty `0x12345678` od adresu 0 | | | + +VCD Task03: `________________________________________` diff --git a/doc/assets/number-storage.png b/doc/assets/number-storage.png new file mode 100644 index 0000000000000000000000000000000000000000..a730868bbd85d54fe6f45e00aa539f82091c0408 GIT binary patch literal 104355 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

ZZ+S=NhFbz|2H60!$Pq!~WUOI+Z=V{UD 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 ND6euSTlTrf{{Y$-Sg`;A literal 0 HcmV?d00001 diff --git a/doc/assets/number-storage.svg b/doc/assets/number-storage.svg new file mode 100644 index 0000000..971c929 --- /dev/null +++ b/doc/assets/number-storage.svg @@ -0,0 +1,10 @@ + + Od wartości do bajtów pamięciWartość 173 ma reprezentację binarną i szesnastkową. Słowo 0x12345678 jest zapisane w pamięci little-endian. + + + 173₁₀wartość + 1010 1101₂AD₁₆te same bity + 0x1234567832-bitowe słowo + 78563412addr+0addr+1addr+2addr+3 + little-endian: najmłodszy bajt pod najniższym adresem + diff --git a/doc/generated/main.tex b/doc/generated/main.tex new file mode 100644 index 0000000..5e23b91 --- /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}{01} +\newcommand{\CardCount}{09} +\newcommand{\CardSlug}{number-systems} +\newcommand{\CardVersion}{v00.01} +\newcommand{\CardStatus}{Gotowa do wykonania} +\newcommand{\DocumentUUID}{d132962b-7deb-5605-8567-d5d35f688230} + +\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 L01 · Number Systems and Storage}\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 01/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 d132962b-7deb-5605-8567-d5d35f688230}\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 d132962b-7deb-5605-8567-d5d35f688230}\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-number-systems}{\nolinkurl{https://gitea.local/edu-inf/lab-rv32i-asm-number-systems}}}} \\% + \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ń łączy wartość z reprezentacją bitową, rozróżnia rozszerzenie znaku od zerowego i wyjaśnia zapis 32-bitowego słowa w pamięci little-endian. + +\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{1} & \texttt{Task01} & wartość, bin, hex i wagi & obowiązkowe & opracowane & \texttt{v00.01} \\ \hline +\texttt{1} & \texttt{Task02} & sign extension i shifts & obowiązkowe & opracowane & \texttt{v00.01} \\ \hline +\texttt{1} & \texttt{Task03} & little-endian storage & 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 · Wartość i reprezentacja} +\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}{RV01.WE01.OG.IP.01 | WE 01: Bits and storage. Podstawy kodowania liczb i pamięci dla RV32I.\textCR | EN IP RV01.01: Uczeń analizuje bity i system pozycyjny. | KW IP RV01.01: Odtwarza wartość\textCR z wag bitów.} \pdftooltip[width=\textwidth]{D2}{RV01.WE01.TECH.I4.01 | WE 01: Bits and storage. Podstawy kodowania liczb i pamięci dla\textCR RV32I. | EK INF04 RV01.01: Uczeń implementuje semantykę danych RV32I. | KW INF04 RV01.01:\textCR Weryfikuje extension, shifts i endian.}\par +\vspace{0.10em}% +\noindent K1: Potwierdź binary=10101101, hex=AD i weighted=173.\quad D1\par +\par\vspace{0.18em}% +\endgroup + +Rozłóż 173 na wagi bitów. Potwierdź, że 10101101 w bazie 2 i AD w bazie 16 opisują dokładnie te same osiem bitów. +\begin{figure}[H] + \centering + \includegraphics[width=0.72\linewidth]{\detokenize{../assets/number-storage.png}} + \caption{Reprezentacja zachowuje wartość; endian określa kolejność bajtów w pamięci.} + \label{fig:number-storage} +\end{figure} +\ESCSectionBlockEnd + +\ESCSectionBlockStart +\section{Task02 · Rozszerzenia i przesunięcia} +\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}{RV01.WE01.OG.IP.01 | WE 01: Bits and storage. Podstawy kodowania liczb i pamięci dla RV32I.\textCR | EN IP RV01.01: Uczeń analizuje bity i system pozycyjny. | KW IP RV01.01: Odtwarza wartość\textCR z wag bitów.} \pdftooltip[width=\textwidth]{D2}{RV01.WE01.TECH.I4.01 | WE 01: Bits and storage. Podstawy kodowania liczb i pamięci dla\textCR RV32I. | EK INF04 RV01.01: Uczeń implementuje semantykę danych RV32I. | KW INF04 RV01.01:\textCR Weryfikuje extension, shifts i endian.}\par +\vspace{0.10em}% +\noindent K1: Zmierz zext=128, sext=-128, lsr1=64 i asr1=-64.\quad D2\par +\par\vspace{0.18em}% +\endgroup + +Dla bajtu 0x80 porównaj zero extension z sign extension oraz logical shift right z arithmetic shift right. Wypełnienie zerem i bitem znaku daje różne liczby. +\ESCSectionBlockEnd + +\ESCSectionBlockStart +\section{Task03 · Słowo w pamięci} +\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}{RV01.WE01.OG.IP.01 | WE 01: Bits and storage. Podstawy kodowania liczb i pamięci dla RV32I.\textCR | EN IP RV01.01: Uczeń analizuje bity i system pozycyjny. | KW IP RV01.01: Odtwarza wartość\textCR z wag bitów.} \pdftooltip[width=\textwidth]{D2}{RV01.WE01.TECH.I4.01 | WE 01: Bits and storage. Podstawy kodowania liczb i pamięci dla\textCR RV32I. | EK INF04 RV01.01: Uczeń implementuje semantykę danych RV32I. | KW INF04 RV01.01:\textCR Weryfikuje extension, shifts i endian.}\par +\vspace{0.10em}% +\noindent K1: Potwierdź bytes=78,56,34,12, roundtrip=1 i zapisz VCD.\quad D2\par +\par\vspace{0.18em}% +\endgroup + +Zapisz 0x12345678 pod adresem 0 w pamięci bajtowej. Little-endian umieszcza bajty 78,56,34,12 pod rosnącymi adresami; odczyt słowa musi odtworzyć wartość. +\ESCSectionBlockEnd + +\end{document} diff --git a/doc/main.tex b/doc/main.tex new file mode 100644 index 0000000..4b7edc5 --- /dev/null +++ b/doc/main.tex @@ -0,0 +1,7 @@ +\newcommand{\PublisherDomain}{mpabi} +\newcommand{\CardArea}{inf} +\newcommand{\CardSeries}{rv32i-asm} +\newcommand{\CardNumber}{01} +\newcommand{\CardSlug}{number-systems} +\newcommand{\CardVersion}{v00.01} +\newcommand{\DocumentUUID}{d132962b-7deb-5605-8567-d5d35f688230} 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..dda13dd --- /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-01-number-systems", "series": "rv32i-asm", "series_title": "ASM · RV32I From Blinker to RISC-V", "number": "01", "count": "09", "slug": "number-systems", "title": "L01 · Number Systems and Storage", "topic": "Systemy pozycyjne, rozszerzenia, przesunięcia i zapis little-endian", "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": "d132962b-7deb-5605-8567-d5d35f688230", "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 · RV32I", "standard": "ISO 7200", "prepared_by": "M. Pabiszczak", "prepared_on": "21.07.2026", "checked_by": "testy automatyczne", "approved_by": "—", "url": "http://localhost:8080", "repository_url": "https://gitea.local/edu-inf/lab-rv32i-asm-number-systems", "revision": "1", "issued_on": "21.07.2026", "series": "RV32I-ASM-01", "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ń łączy wartość z reprezentacją bitową, rozróżnia rozszerzenie znaku od zerowego i wyjaśnia zapis 32-bitowego słowa w pamięci little-endian.", "scope_title": "Zakres tasków", "scope_table": {"headers": ["Krok", "Numer tasku", "Najważniejsza idea", "Priorytet", "Status", "Version"], "rows": [{"chapter": "1", "task": "Task01", "idea_tex": "wartość, bin, hex i wagi", "priority": "obowiązkowe", "status": "ready", "version": "v00.01"}, {"chapter": "1", "task": "Task02", "idea_tex": "sign extension i shifts", "priority": "obowiązkowe", "status": "ready", "version": "v00.01"}, {"chapter": "1", "task": "Task03", "idea_tex": "little-endian storage", "priority": "obowiązkowe", "status": "ready", "version": "v00.01"}]}}, + "learning_effects": {"RV01.EN01": {"text": "Uczeń analizuje równoważne zapisy wartości oraz znaczenie bitu znaku.", "label": "Reprezentacja", "bloom_level": "Analiza", "assessment_criteria": ["RV01.KW01"]}, "RV01.EK01": {"text": "Uczeń implementuje rozszerzenia, przesunięcia i zapis słowa w pamięci.", "label": "Storage semantics", "bloom_level": "Zastosowanie", "assessment_criteria": ["RV01.KW01"]}}, + "assessment_criteria": {"RV01.KW01": {"text": "Wartość 173, wyniki dla 0x80 i kolejność bajtów słowa 0x12345678 są potwierdzone niezależnym modelem i self-checking RTL.", "learning_effects": ["RV01.EN01", "RV01.EK01"]}}, + "educational_requirements": {"RV01.WE01": {"text": "Podstawy kodowania liczb i pamięci dla RV32I.", "label": "Bits and storage", "learning_effects": ["RV01.EN01", "RV01.EK01"], "learning_tree": {"schema": "we-learning-tree.v1", "policy": "Najpierw przewidywanie wartości, potem pomiar w modelu i RTL; VCD jest dowodem przebiegu, nie dekoracją.", "ogolne": [{"tree_id": "RV01.WE01.OG.IP.01", "effect_ref": "RV01.EN01", "display": "EN IP RV01.01", "text": "Uczeń analizuje bity i system pozycyjny.", "kw": [{"criterion_ref": "RV01.KW01", "display": "KW IP RV01.01", "text": "Odtwarza wartość z wag bitów."}]}], "zawodowe": [{"tree_id": "RV01.WE01.TECH.I4.01", "effect_ref": "RV01.EK01", "display": "EK INF04 RV01.01", "text": "Uczeń implementuje semantykę danych RV32I.", "kw": [{"criterion_ref": "RV01.KW01", "display": "KW INF04 RV01.01", "text": "Weryfikuje extension, shifts i endian."}]}]}}}, + "sections": [ + {"title": "Task01 · Wartość i reprezentacja", "content_kind": "prose", "content_tex": "Rozłóż 173 na wagi bitów. Potwierdź, że 10101101 w bazie 2 i AD w bazie 16 opisują dokładnie te same osiem bitów.", "educational_requirement_refs": ["RV01.WE01"], "learning_effect_refs": ["RV01.EN01", "RV01.EK01"], "assessment_criterion_refs": ["RV01.KW01"], "area_tree_refs": ["RV01.WE01.OG.IP.01", "RV01.WE01.TECH.I4.01"], "steps": [{"id": "T01-POSITION", "title": "Potwierdź binary=10101101, hex=AD i weighted=173.", "tree_refs": ["RV01.WE01.OG.IP.01"]}], "assets": [{"path": "assets/number-storage.png", "html_path": "assets/number-storage.svg", "caption": "Reprezentacja zachowuje wartość; endian określa kolejność bajtów w pamięci.", "label": "fig:number-storage", "alt": "Diagram wartości, bitów, hex i bajtów little-endian.", "kind": "diagram", "width": 0.72}]}, + {"title": "Task02 · Rozszerzenia i przesunięcia", "content_kind": "prose", "content_tex": "Dla bajtu 0x80 porównaj zero extension z sign extension oraz logical shift right z arithmetic shift right. Wypełnienie zerem i bitem znaku daje różne liczby.", "educational_requirement_refs": ["RV01.WE01"], "learning_effect_refs": ["RV01.EN01", "RV01.EK01"], "assessment_criterion_refs": ["RV01.KW01"], "area_tree_refs": ["RV01.WE01.OG.IP.01", "RV01.WE01.TECH.I4.01"], "steps": [{"id": "T02-EXTEND", "title": "Zmierz zext=128, sext=-128, lsr1=64 i asr1=-64.", "tree_refs": ["RV01.WE01.TECH.I4.01"]}]}, + {"title": "Task03 · Słowo w pamięci", "content_kind": "prose", "content_tex": "Zapisz 0x12345678 pod adresem 0 w pamięci bajtowej. Little-endian umieszcza bajty 78,56,34,12 pod rosnącymi adresami; odczyt słowa musi odtworzyć wartość.", "educational_requirement_refs": ["RV01.WE01"], "learning_effect_refs": ["RV01.EN01", "RV01.EK01"], "assessment_criterion_refs": ["RV01.KW01"], "area_tree_refs": ["RV01.WE01.OG.IP.01", "RV01.WE01.TECH.I4.01"], "steps": [{"id": "T03-STORAGE", "title": "Potwierdź bytes=78,56,34,12, roundtrip=1 i zapisz VCD.", "tree_refs": ["RV01.WE01.TECH.I4.01"]}]} + ], + "tasks": {"task01": {"prompt_tex": "Przelicz 173 między bazami i wagami.", "criterion": "value=173 binary=10101101 hex=AD weighted=173", "assessment_criterion_ref": "RV01.KW01"}, "task02": {"prompt_tex": "Porównaj rozszerzenia i przesunięcia 0x80.", "criterion": "zext=128 sext=-128 lsr1=64 asr1=-64", "assessment_criterion_ref": "RV01.KW01"}, "task03": {"prompt_tex": "Zapisz słowo little-endian i odczytaj je ponownie.", "criterion": "bytes=78,56,34,12 endian=little roundtrip=1", "assessment_criterion_ref": "RV01.KW01"}}, + "tasks_order": ["task01", "task02", "task03"], "mission": "Zbudować precyzyjny język bitów, liczb i pamięci przed dekodowaniem RV32I.", "objectives": ["Przeliczysz zapis binarny i hex.", "Wyjaśnisz sign extension i shifts.", "Odtworzysz słowo z bajtów little-endian."], + "agenda": [{"time": "0--20 min", "work": "Bazy i wagi."}, {"time": "20--40 min", "work": "Extension i shifts."}, {"time": "40--65 min", "work": "Pamięć i VCD."}], + "tech_stack": {"software": [{"name": "Python", "use": "niezależny model"}, {"name": "SystemVerilog + Verilator", "use": "self-checking RTL i VCD"}, {"name": "stemctl", "use": "profile native-amd64 i hazard3-sim"}], "hardware": [{"name": "Brak", "use": "karta przygotowawcza"}]}, + "container_test": {"title": "Dwa niezależne wykonania", "commands": ["stemctl test native-amd64 rv32i-asm L01 1", "stemctl test hazard3-sim rv32i-asm L01 2", "stemctl debug hazard3-sim rv32i-asm L01 3"], "pass_condition": "Zgodne komunikaty PASS oraz niepusty VCD Task03."}, + "safety_rules": [{"title": "Nie mieszaj wartości z zapisem", "body": "Baza zmienia notację, nie przechowywaną wartość."}, {"title": "Jawna szerokość", "body": "Każde rozszerzenie i przesunięcie ma określoną szerokość oraz signedness."}], + "hardware_procedure": [{"step": "1", "action": "Przewiduj.", "condition": "Zapisz bity i liczby przed testem."}, {"step": "2", "action": "Zmierz dwoma modelami.", "condition": "Model i RTL są zgodne."}, {"step": "3", "action": "Zachowaj dowód.", "condition": "PASS i VCD wskazują te same wartości."}], "references": [] +} diff --git a/lesson-flow.yaml b/lesson-flow.yaml new file mode 100644 index 0000000..45be19a --- /dev/null +++ b/lesson-flow.yaml @@ -0,0 +1,28 @@ +schema: 1 +card: rv32i-asm/L01 +title: Number Systems and Storage +steps: + - id: positional + task: task01_positional_encoding + action: test + profile: hazard3-sim + target: hazard3-baremetal + command: stemctl test hazard3-sim rv32i-asm L01 1 + board: {show: [value, bits, hex_digits, bit_weights], highlight: [base, position, representation], question: Dlaczego zapis binarny i szesnastkowy opisuje te same osiem bitów?} + evidence: [binary, hex, weighted_sum] + - id: extension + task: task02_extension_shifts + action: test + profile: hazard3-sim + target: hazard3-baremetal + command: stemctl test hazard3-sim rv32i-asm L01 2 + board: {show: [input_0x80, zero_extension, sign_extension, logical_shift, arithmetic_shift], highlight: [sign_bit, fill_bit], question: Skąd bierze się różnica między 64 i -64?} + evidence: [zext, sext, lsr1, asr1] + - id: storage + task: task03_little_endian_memory + action: debug + profile: hazard3-sim + target: hazard3-baremetal + command: stemctl debug hazard3-sim rv32i-asm L01 3 + board: {show: [word, byte_addresses, write_edge, read_word, trace_vcd], highlight: [least_significant_byte, little_endian, roundtrip], question: Który bajt trafia pod najniższy adres?} + evidence: [bytes, endian, roundtrip, trace_vcd] diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9890114 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,811 @@ +{ + "name": "mpabi-inf-rv32i-asm-01-number-systems", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mpabi-inf-rv32i-asm-01-number-systems", + "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..25907dc --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "mpabi-inf-rv32i-asm-01-number-systems", + "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_positional_encoding.sv b/src/tasks/task01_positional_encoding.sv new file mode 100644 index 0000000..6bf3b24 --- /dev/null +++ b/src/tasks/task01_positional_encoding.sv @@ -0,0 +1,12 @@ +module positional_encoding( + input logic [7:0] value, + output logic [7:0] bits, + output logic [3:0] hex_high, + output logic [3:0] hex_low, + output logic [8:0] weighted_sum +); + assign bits = value; + assign hex_high = value[7:4]; + assign hex_low = value[3:0]; + assign weighted_sum = {1'b0, value}; +endmodule diff --git a/src/tasks/task02_extension_shifts.sv b/src/tasks/task02_extension_shifts.sv new file mode 100644 index 0000000..340673e --- /dev/null +++ b/src/tasks/task02_extension_shifts.sv @@ -0,0 +1,13 @@ +module extension_shifts( + input logic [7:0] value, + input logic [2:0] shift, + output logic [31:0] zero_extended, + output logic [31:0] sign_extended, + output logic [31:0] logical_right, + output logic [31:0] arithmetic_right +); + assign zero_extended = {24'b0, value}; + assign sign_extended = {{24{value[7]}}, value}; + assign logical_right = zero_extended >> shift; + assign arithmetic_right = $signed(sign_extended) >>> shift; +endmodule diff --git a/src/tasks/task03_little_endian_memory.sv b/src/tasks/task03_little_endian_memory.sv new file mode 100644 index 0000000..010faf2 --- /dev/null +++ b/src/tasks/task03_little_endian_memory.sv @@ -0,0 +1,21 @@ +module little_endian_memory( + input logic clk, input logic reset, input logic write_enable, + input logic [2:0] address, input logic [31:0] write_word, + output logic [31:0] read_word, + output logic [7:0] byte0, byte1, byte2, byte3 +); + logic [7:0] memory [0:7]; + integer index; + always_ff @(posedge clk) begin + if (reset) begin + for (index = 0; index < 8; index = index + 1) memory[index] <= 8'h00; + end else if (write_enable) begin + memory[address] <= write_word[7:0]; + memory[address + 1] <= write_word[15:8]; + memory[address + 2] <= write_word[23:16]; + memory[address + 3] <= write_word[31:24]; + end + end + assign read_word = {memory[address + 3], memory[address + 2], memory[address + 1], memory[address]}; + assign byte0 = memory[0]; assign byte1 = memory[1]; assign byte2 = memory[2]; assign byte3 = memory[3]; +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..e3eea21 --- /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('L01 identity and three tasks are stable',()=>{assert.equal(source.card.series,'rv32i-asm');assert.equal(source.card.number,'01');assert.equal(source.card.uuid,'d132962b-7deb-5605-8567-d5d35f688230');assert.deepEqual(source.tasks_order,['task01','task02','task03']);}); +test('number, extension and storage evidence are explicit',()=>{const text=JSON.stringify(source);for(const token of ['10101101','sign extension','little-endian','78,56,34,12','VCD'])assert.match(text,new RegExp(token,'i'));}); +test('all task sources exist',async()=>{for(const name of ['task01_positional_encoding','task02_extension_shifts','task03_little_endian_memory'])await access(new URL(`../src/tasks/${name}.sv`,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..fea95af --- /dev/null +++ b/tests/reference_model.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +"""Independent reference models for L01; no RTL source is imported.""" +import sys + + +def task01(): + value = 173 + bits = format(value, "08b") + weighted = sum(((value >> bit) & 1) << bit for bit in range(8)) + assert bits == "10101101" and format(value, "02X") == "AD" and weighted == value + print("PASS task01 value=173 binary=10101101 hex=AD weighted=173") + + +def task02(): + value = 0x80 + signed = value - 0x100 if value & 0x80 else value + zext = value + sext = signed + lsr1 = value >> 1 + asr1 = signed >> 1 + assert (zext, sext, lsr1, asr1) == (128, -128, 64, -64) + print("PASS task02 input=0x80 zext=128 sext=-128 lsr1=64 asr1=-64") + + +def task03(): + word = 0x12345678 + memory = [(word >> (8 * index)) & 0xFF for index in range(4)] + roundtrip = sum(byte << (8 * index) for index, byte in enumerate(memory)) + assert memory == [0x78, 0x56, 0x34, 0x12] and roundtrip == word + print("PASS task03 word=0x12345678 bytes=78,56,34,12 endian=little roundtrip=1") + + +TASKS = {"task01_positional_encoding": task01, "task02_extension_shifts": task02, "task03_little_endian_memory": 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_positional_encoding_tb.sv b/tests/task01_positional_encoding_tb.sv new file mode 100644 index 0000000..2087fef --- /dev/null +++ b/tests/task01_positional_encoding_tb.sv @@ -0,0 +1,10 @@ +module tb; + logic [7:0] value, bits; logic [3:0] hex_high, hex_low; logic [8:0] weighted_sum; string trace_file; + positional_encoding dut(.*); + initial begin + if ($value$plusargs("trace=%s", trace_file)) begin $dumpfile(trace_file); $dumpvars(0, tb); end + value = 8'hAD; #1; + if (bits !== 8'b10101101 || hex_high !== 4'hA || hex_low !== 4'hD || weighted_sum !== 9'd173) $fatal(1, "encoding mismatch"); + $display("PASS task01 value=173 binary=10101101 hex=AD weighted=173"); $finish; + end +endmodule diff --git a/tests/task02_extension_shifts_tb.sv b/tests/task02_extension_shifts_tb.sv new file mode 100644 index 0000000..35ef49d --- /dev/null +++ b/tests/task02_extension_shifts_tb.sv @@ -0,0 +1,10 @@ +module tb; + logic [7:0] value; logic [2:0] shift; logic [31:0] zero_extended, sign_extended, logical_right, arithmetic_right; string trace_file; + extension_shifts dut(.*); + initial begin + if ($value$plusargs("trace=%s", trace_file)) begin $dumpfile(trace_file); $dumpvars(0, tb); end + value = 8'h80; shift = 3'd1; #1; + if (zero_extended !== 32'd128 || $signed(sign_extended) !== -128 || logical_right !== 32'd64 || $signed(arithmetic_right) !== -64) $fatal(1, "extension/shift mismatch"); + $display("PASS task02 input=0x80 zext=128 sext=-128 lsr1=64 asr1=-64"); $finish; + end +endmodule diff --git a/tests/task03_little_endian_memory_tb.sv b/tests/task03_little_endian_memory_tb.sv new file mode 100644 index 0000000..b1c7867 --- /dev/null +++ b/tests/task03_little_endian_memory_tb.sv @@ -0,0 +1,11 @@ +module tb; + logic clk=0, reset, write_enable; logic [2:0] address; logic [31:0] write_word, read_word; logic [7:0] byte0, byte1, byte2, byte3; string trace_file; + little_endian_memory dut(.*); always #5 clk = ~clk; + initial begin + if ($value$plusargs("trace=%s", trace_file)) begin $dumpfile(trace_file); $dumpvars(0, tb); end + reset=1; write_enable=0; address=0; write_word=0; @(posedge clk); #1; reset=0; + write_word=32'h12345678; write_enable=1; @(posedge clk); #1; write_enable=0; + if ({byte3,byte2,byte1,byte0} !== 32'h12345678 || read_word !== 32'h12345678) $fatal(1, "little-endian mismatch"); + $display("PASS task03 word=0x12345678 bytes=78,56,34,12 endian=little roundtrip=1"); $finish; + end +endmodule diff --git a/tests/test_rtl.sh b/tests/test_rtl.sh new file mode 100755 index 0000000..3672b5f --- /dev/null +++ b/tests/test_rtl.sh @@ -0,0 +1,13 @@ +#!/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_positional_encoding|task02_extension_shifts|task03_little_endian_memory) ;; *) 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/tools/card-action.sh b/tools/card-action.sh new file mode 100755 index 0000000..49adccd --- /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_positional_encoding task02_extension_shifts task03_little_endian_memory) +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" ;; + 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..d610b5a --- /dev/null +++ b/web/card-data.json @@ -0,0 +1,2751 @@ +{ + "schema": "esc-card-react-view.v1", + "card": { + "id": "mpabi-inf-rv32i-asm-01-number-systems", + "series": "rv32i-asm", + "series_title": "ASM · RV32I From Blinker to RISC-V", + "number": "01", + "count": "09", + "slug": "number-systems", + "title": "L01 · Number Systems and Storage", + "topic": "Systemy pozycyjne, rozszerzenia, przesunięcia i zapis little-endian", + "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": "d132962b-7deb-5605-8567-d5d35f688230", + "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": true, + "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": "", + "local_href": "", + "resource_uuid": "", + "available": false, + "current": false, + "number": "02", + "version": "", + "status": "planned", + "tasks": [] + }, + { + "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": "
TITLEL01 · Number Systems and Storage
VER.1
DATETIME21.07.2026
PROJECTFrom Blinker to RISC-V
SERIESASM · RV32I From Blinker to RISC-V
CARD01/09
SHEET1/5
SUBJ.Inf.
PROG.
CORE
SCOPE
LEVEL
POS.
GITEA UUIDd132962b-7deb-5605-8567-d5d35f688230CARD UUIDd132962b-7deb-5605-8567-d5d35f688230AUTHOR  M. Pabiszczak
QRHTML wyłączony
", + "footer_html": "
UUID d132962b-7deb-5605-8567-d5d35f688230M. PabiszczakSTRONA 1/5
", + "left_margin_html": "
TECH
", + "right_margin_html": "
OG
", + "goal_title": "Cel karty", + "goal_html": "

Uczeń łączy wartość z reprezentacją bitową, rozróżnia rozszerzenie znaku od zerowego i wyjaśnia zapis 32-bitowego słowa w pamięci little-endian.

", + "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": "1", + "task": "Task01", + "idea_html": "

wartość, bin, hex i wagi

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

sign extension i shifts

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

little-endian storage

", + "priority": "obowiązkowe", + "status": "ready", + "status_label": "opracowane", + "version": "v00.01", + "key": false + } + ] + }, + "sections": [ + { + "id": "section-1-content", + "index": 1, + "title": "Task01 · Wartość i reprezentacja", + "show_heading": true, + "toc_entry": true, + "header_html": "
TITLEL01 · Number Systems and Storage
VER.1
DATETIME21.07.2026
PROJECTFrom Blinker to RISC-V
SERIESASM · RV32I From Blinker to RISC-V
CARD01/09
SHEET2/5
SUBJ.Inf.
PROG.
CORE
SCOPE
LEVEL
POS.
GITEA UUIDd132962b-7deb-5605-8567-d5d35f688230CARD UUIDd132962b-7deb-5605-8567-d5d35f688230AUTHOR  M. Pabiszczak
QRHTML wyłączony
", + "footer_html": "
UUID d132962b-7deb-5605-8567-d5d35f688230M. PabiszczakSTRONA 2/5
", + "left_margin_html": "
TECH
", + "right_margin_html": "
OG
", + "content_html": "

Rozłóż 173 na wagi bitów. Potwierdź, że 10101101 w bazie 2 i AD w bazie 16 opisują dokładnie te same osiem bitów.

", + "tasks": [], + "assets": [ + { + "label": "fig:number-storage", + "logical_label": "fig:number-storage", + "figure_number": 1, + "caption": "Reprezentacja zachowuje wartość; endian określa kolejność bajtów w pamięci.", + "alt": "Diagram wartości, bitów, hex i bajtów little-endian.", + "href": "figures/number-storage.svg", + "source_href": "", + "width": 0.72, + "kind": "diagram", + "interactive": null, + "tile": null, + "diagram_title": "Reprezentacja zachowuje wartość; endian określa kolejność bajtów w pamięci.", + "diagram_block_label": "", + "diagram_description": "", + "diagram_phase_labels": [] + } + ], + "steps": [ + { + "id": "T01-POSITION", + "title": "Potwierdź binary=10101101, hex=AD i weighted=173." + } + ], + "spread_group": "", + "spread_label": "", + "spread_columns": 1, + "spread_index": 1, + "spread_count": 1, + "page_orientation": "portrait" + }, + { + "id": "section-2-content", + "index": 2, + "title": "Task02 · Rozszerzenia i przesunięcia", + "show_heading": true, + "toc_entry": true, + "header_html": "
TITLEL01 · Number Systems and Storage
VER.1
DATETIME21.07.2026
PROJECTFrom Blinker to RISC-V
SERIESASM · RV32I From Blinker to RISC-V
CARD01/09
SHEET3/5
SUBJ.Inf.
PROG.
CORE
SCOPE
LEVEL
POS.
GITEA UUIDd132962b-7deb-5605-8567-d5d35f688230CARD UUIDd132962b-7deb-5605-8567-d5d35f688230AUTHOR  M. Pabiszczak
QRHTML wyłączony
", + "footer_html": "
UUID d132962b-7deb-5605-8567-d5d35f688230M. PabiszczakSTRONA 3/5
", + "left_margin_html": "
TECH
", + "right_margin_html": "
OG
", + "content_html": "

Dla bajtu 0x80 porównaj zero extension z sign extension oraz logical shift right z arithmetic shift right. Wypełnienie zerem i bitem znaku daje różne liczby.

", + "tasks": [], + "assets": [], + "steps": [ + { + "id": "T02-EXTEND", + "title": "Zmierz zext=128, sext=-128, lsr1=64 i asr1=-64." + } + ], + "spread_group": "", + "spread_label": "", + "spread_columns": 1, + "spread_index": 1, + "spread_count": 1, + "page_orientation": "portrait" + }, + { + "id": "section-3-content", + "index": 3, + "title": "Task03 · Słowo w pamięci", + "show_heading": true, + "toc_entry": true, + "header_html": "
TITLEL01 · Number Systems and Storage
VER.1
DATETIME21.07.2026
PROJECTFrom Blinker to RISC-V
SERIESASM · RV32I From Blinker to RISC-V
CARD01/09
SHEET4/5
SUBJ.Inf.
PROG.
CORE
SCOPE
LEVEL
POS.
GITEA UUIDd132962b-7deb-5605-8567-d5d35f688230CARD UUIDd132962b-7deb-5605-8567-d5d35f688230AUTHOR  M. Pabiszczak
QRHTML wyłączony
", + "footer_html": "
UUID d132962b-7deb-5605-8567-d5d35f688230M. PabiszczakSTRONA 4/5
", + "left_margin_html": "
TECH
", + "right_margin_html": "
OG
", + "content_html": "

Zapisz 0x12345678 pod adresem 0 w pamięci bajtowej. Little-endian umieszcza bajty 78,56,34,12 pod rosnącymi adresami; odczyt słowa musi odtworzyć wartość.

", + "tasks": [], + "assets": [], + "steps": [ + { + "id": "T03-STORAGE", + "title": "Potwierdź bytes=78,56,34,12, roundtrip=1 i zapisz VCD." + } + ], + "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": "
TITLEL01 · Number Systems and Storage
VER.1
DATETIME21.07.2026
PROJECTFrom Blinker to RISC-V
SERIESASM · RV32I From Blinker to RISC-V
CARD01/09
SHEET5/5
SUBJ.Inf.
PROG.
CORE
SCOPE
LEVEL
POS.
GITEA UUIDd132962b-7deb-5605-8567-d5d35f688230CARD UUIDd132962b-7deb-5605-8567-d5d35f688230AUTHOR  M. Pabiszczak
QRHTML wyłączony
", + "footer_html": "
UUID d132962b-7deb-5605-8567-d5d35f688230M. PabiszczakSTRONA 5/5
", + "content_html": "
WE 01 Bits and storage

Podstawy kodowania liczb i pamięci dla RV32I.

OG

EN IP RV01.01 Uczeń analizuje bity i system pozycyjny.
  • KW IP RV01.01 Odtwarza wartość z wag bitów.

TECH

EK INF04 RV01.01 Uczeń implementuje semantykę danych RV32I.
  • KW INF04 RV01.01 Weryfikuje extension, shifts i endian.
" + }, + "toc": [ + { + "id": "section-1-content", + "label": "1. Task01 · Wartość i reprezentacja" + }, + { + "id": "section-2-content", + "label": "2. Task02 · Rozszerzenia i przesunięcia" + }, + { + "id": "section-3-content", + "label": "3. Task03 · Słowo w pamięci" + } + ] +} diff --git a/web/figures/number-storage.svg b/web/figures/number-storage.svg new file mode 100644 index 0000000..971c929 --- /dev/null +++ b/web/figures/number-storage.svg @@ -0,0 +1,10 @@ + + Od wartości do bajtów pamięciWartość 173 ma reprezentację binarną i szesnastkową. Słowo 0x12345678 jest zapisane w pamięci little-endian. + + + 173₁₀wartość + 1010 1101₂AD₁₆te same bity + 0x1234567832-bitowe słowo + 78563412addr+0addr+1addr+2addr+3 + little-endian: najmłodszy bajt pod najniższym adresem + 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..e4e1ef2 --- /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 L01 · Number Systems and Storage 1 21.07.2026 \A PROJECT SERIES CARD SHEET \A \A From Blinker to RISC-V ASM · RV32I From Blinker to RISC-V 01/09 " counter(page) "/" counter(pages) " \A SUBJ. PROG. CORE SCOPE LEVEL POS. GITEA UUID d132962b-7deb-5605-8567-d5d35f688230\A CARD UUID d132962b-7deb-5605-8567-d5d35f688230 AUTHOR M. Pabiszczak\A Inf. — — — — — \A \A GITEA https://gitea.local/edu-inf/lab-rv32i-asm-number-systems \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 d132962b-7deb-5605-8567-d5d35f688230";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} +}