1
0
mirror of git://f0xx.org/android_cast synced 2026-07-29 04:38:53 +03:00

EB of git flow

This commit is contained in:
Anton Afanasyeu
2026-05-23 12:36:31 +02:00
parent a5ee5d7140
commit aa05e8e39c
19 changed files with 340 additions and 177 deletions

View File

@@ -24,7 +24,7 @@ Follow [docs/GIT_FLOW.md](docs/GIT_FLOW.md) and [docs/GIT_FLOW.pdf](docs/GIT_FLO
4. **Release:** only after user confirms — merge `next` → `master`, CI green, **tag on master**, then **merge master → next**. 4. **Release:** only after user confirms — merge `next` → `master`, CI green, **tag on master**, then **merge master → next**.
5. **Hotfix (production):** branch from **master**, not next; after tag, **merge master into next**. 5. **Hotfix (production):** branch from **master**, not next; after tag, **merge master into next**.
6. **Hotfix (next-only bug):** `fix/*` from **next** → merge to **next** only. 6. **Hotfix (next-only bug):** `fix/*` from **next** → merge to **next** only.
7. **Never** force-push `master` or `next`. 7. **Never** force-push `master`; force-push `next` only when the user explicitly requests history rewrite on integration.
8. When working on a named branch, call **SetActiveBranch** with that branch name. 8. When working on a named branch, call **SetActiveBranch** with that branch name.
## Quick commands ## Quick commands

View File

@@ -92,4 +92,4 @@ Activities bind via AIDL (`ICastSenderService` / `ICastReceiverService`) for sta
We use a **green `master`** plus integration branch **`next`**: features merge to `next` first; releases merge `next``master` and are tagged there; production hotfixes branch from `master` and sync back into `next`. We use a **green `master`** plus integration branch **`next`**: features merge to `next` first; releases merge `next``master` and are tagged there; production hotfixes branch from `master` and sync back into `next`.
- Narrative: [docs/GIT_FLOW.md](docs/GIT_FLOW.md) - Narrative: [docs/GIT_FLOW.md](docs/GIT_FLOW.md)
- PDF (diagrams): [docs/GIT_FLOW.pdf](docs/GIT_FLOW.pdf) — regenerate: `python3 docs/_pdf_build/build_git_flow_pdf.py` - PDF: [docs/GIT_FLOW.pdf](docs/GIT_FLOW.pdf) — `pip install -r docs/_pdf_build/requirements-pdf.txt` + `rsvg-convert`, then `python3 docs/_pdf_build/build_git_flow_pdf.py` (SVG sources alongside for vector view)

View File

@@ -1,7 +1,13 @@
# Android Cast — Git flow (green `master`) # Android Cast — Git flow (green `master`)
**Version:** 1.0 · **Last updated:** 2026-05-21 | Field | Value |
**PDF:** regenerate with `python3 docs/_pdf_build/build_git_flow_pdf.py``docs/GIT_FLOW.pdf` |-------|-------|
| **Version** | 1.1 |
| **Published** | 2026-05-21 |
| **Author(s)** | Anton Afanaasyeu |
| **Markdown source** | `docs/GIT_FLOW.md` |
| **PDF** | `docs/GIT_FLOW.pdf``python3 docs/_pdf_build/build_git_flow_pdf.py` |
| **Diagrams (vector)** | `docs/_pdf_build/git_flow_diagram_*.svg` — open in Okular for crisp zoom |
This document defines how we develop, integrate, release, and hotfix. **Cursor agents and humans follow the same rules.** This document defines how we develop, integrate, release, and hotfix. **Cursor agents and humans follow the same rules.**
@@ -66,7 +72,7 @@ flowchart LR
3. **Commit** in small logical chunks on the topic branch. 3. **Commit** in small logical chunks on the topic branch.
4. **Before merge:** run project checks (Android: `./gradlew :app:testDebugUnitTest` or CI equivalent; backend: relevant tests/lint). 4. **Before merge:** run project checks (Android: `./gradlew :app:testDebugUnitTest` or CI equivalent; backend: relevant tests/lint).
5. **Merge to `next`:** prefer merge commit or squash per team habit; **do not** merge unfinished work. 5. **Merge to `next`:** prefer merge commit or squash per team habit; **do not** merge unfinished work.
6. **Delete** topic branch after merge (local + remote). 6. **Topic branch cleanup:** optional locally (`git branch -d feature/...`). On the server, prefer **delete branch on merge** (Gitea/GitLab/GitHub) so merged `feature/*` / `fix/*` are removed on `origin` automatically.
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
@@ -166,6 +172,7 @@ flowchart TD
| No direct pushes of WIP | ✓ | ✓ | — | | No direct pushes of WIP | ✓ | ✓ | — |
| Only maintainers merge to `master` | ✓ | optional | — | | Only maintainers merge to `master` | ✓ | optional | — |
| Tag only on `master` | ✓ | — | — | | Tag only on `master` | ✓ | — | — |
| Force-push | **never** | **maintainer only, rare** | allowed on topic branches |
See **[§7.1 How to protect branches](#71-how-to-protect-branches-not-local-hooks-alone)** below. See **[§7.1 How to protect branches](#71-how-to-protect-branches-not-local-hooks-alone)** below.
@@ -182,10 +189,12 @@ See **[§7.1 How to protect branches](#71-how-to-protect-branches-not-local-hook
#### What to enable on `master` and `next` #### What to enable on `master` and `next`
1. **No force-push** (no `git push --force` rewriting history). 1. **`master`: never allow force-push** — production history must not be rewritten.
2. **No direct push** (optional but strong): changes only via merge from `feature/*` or from PR/MR — you merge locally or on the web, but the server rejects `git push origin master` with a normal commit on top without review (exact option name varies). 2. **`next`: force-push off by default** — enable only for maintainers when you intentionally clean integration history (squash/rebase of already-merged work). Prefer fixing history on `feature/*` before merging to `next`. After a rare `next` force-push, anyone with a clone must reset: `git fetch origin && git reset --hard origin/next`.
3. **Require CI green** before merge (if you have Actions/GitLab CI/Gitea Actions). 3. **Delete branch on merge** on the server — merged `feature/*` / `fix/*` disappear from `origin` without a manual delete step.
4. **Restrict who can push** to you (and CI deploy key if needed). 4. **No direct push** (optional but strong): changes only via merge from `feature/*` or from PR/MR.
5. **Require CI green** before merge (if you have Actions/GitLab CI/Gitea Actions).
6. **Restrict who can push** to you (and CI deploy key if needed).
`feature/*` branches stay **unprotected** — push freely. `feature/*` branches stay **unprotected** — push freely.
@@ -252,7 +261,7 @@ Agents working in this repo **must**:
4. **Releases:** only describe merging `next` → `master` and tagging after user confirms QA. 4. **Releases:** only describe merging `next` → `master` and tagging after user confirms QA.
5. **Hotfixes on released code:** branch from `master`, merge back to `master`, tag, then **merge `master` into `next`**. 5. **Hotfixes on released code:** branch from `master`, merge back to `master`, tag, then **merge `master` into `next`**.
6. **Set active branch** metadata when using a named topic branch (project convention). 6. **Set active branch** metadata when using a named topic branch (project convention).
7. **Do not** force-push `master` / `next`. 7. **Do not** force-push `master`; avoid force-push on `next` unless the user explicitly requests integration history rewrite.
--- ---

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +1,16 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Build docs/GIT_FLOW.pdf from structured content and Mermaid diagrams. """Build docs/GIT_FLOW.pdf from structured content and Mermaid diagrams.
Source of truth for edits: docs/GIT_FLOW.md (narrative) + this script (PDF layout). Pipeline: Mermaid (.mmd) → SVG → high-DPI PNG (rsvg-convert) → PDF with correct aspect ratio.
Vector sources: docs/_pdf_build/git_flow_diagram_*.svg (open in Okular for crisp zoom).
Requires: reportlab, npx (@mermaid-js/mermaid-cli), rsvg-convert (librsvg).
Regenerate: python3 docs/_pdf_build/build_git_flow_pdf.py Regenerate: python3 docs/_pdf_build/build_git_flow_pdf.py
""" """
from __future__ import annotations from __future__ import annotations
import shutil
import subprocess import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
@@ -16,6 +20,7 @@ from reportlab.lib.enums import TA_LEFT
from reportlab.lib.pagesizes import A4 from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import cm from reportlab.lib.units import cm
from reportlab.lib.utils import ImageReader
from reportlab.platypus import ( from reportlab.platypus import (
Image, Image,
PageBreak, PageBreak,
@@ -30,35 +35,38 @@ ROOT = Path(__file__).resolve().parents[2]
BUILD = Path(__file__).resolve().parent BUILD = Path(__file__).resolve().parent
OUT_PDF = ROOT / "docs" / "GIT_FLOW.pdf" OUT_PDF = ROOT / "docs" / "GIT_FLOW.pdf"
# (mmd, svg, png, caption, mermaid w/h px, max width cm in PDF)
DIAGRAMS = [ DIAGRAMS = [
("git_flow_diagram_branches.mmd", "git_flow_diagram_branches.png", "Branch model (green master)"), ("git_flow_diagram_branches.mmd", "git_flow_diagram_branches.svg", "git_flow_diagram_branches.png",
("git_flow_diagram_feature.mmd", "git_flow_diagram_feature.png", "Daily development"), "Branch model (green master)", 1600, 720, 15.5),
("git_flow_diagram_release.mmd", "git_flow_diagram_release.png", "Release: next → master"), ("git_flow_diagram_feature.mmd", "git_flow_diagram_feature.svg", "git_flow_diagram_feature.png",
("git_flow_diagram_hotfix.mmd", "git_flow_diagram_hotfix.png", "Hotfix decision and flow"), "Daily development", 1100, 1300, 14.0),
("git_flow_diagram_release.mmd", "git_flow_diagram_release.svg", "git_flow_diagram_release.png",
"Release: next → master", 1100, 1300, 14.0),
("git_flow_diagram_hotfix.mmd", "git_flow_diagram_hotfix.svg", "git_flow_diagram_hotfix.png",
"Hotfix decision and flow", 1200, 1000, 14.5),
] ]
MAX_DIAGRAM_HEIGHT_CM = 16.5
RSVG_DPI = 200
DOC_VERSION = "1.1"
DOC_DATE = "2026-05-21"
DOC_AUTHOR = "Anton Afanaasyeu"
DOC_SOURCE = "docs/GIT_FLOW.md"
def render_mermaid() -> None: def render_mermaid() -> None:
for mmd, png, _ in DIAGRAMS: for mmd, svg_name, _, _, w_px, h_px, _ in DIAGRAMS:
src = BUILD / mmd src = BUILD / mmd
dst = BUILD / png dst = BUILD / svg_name
if not src.exists(): if not src.exists():
raise FileNotFoundError(src) raise FileNotFoundError(src)
subprocess.run( subprocess.run(
[ [
"npx", "npx", "--yes", "@mermaid-js/mermaid-cli@11.4.0",
"--yes", "-i", str(src), "-o", str(dst),
"@mermaid-js/mermaid-cli@11.4.0", "-b", "white", "-w", str(w_px), "-H", str(h_px),
"-i",
str(src),
"-o",
str(dst),
"-b",
"white",
"-w",
"1200",
"-H",
"800",
], ],
cwd=str(ROOT), cwd=str(ROOT),
check=True, check=True,
@@ -66,6 +74,31 @@ def render_mermaid() -> None:
) )
def svg_to_png(svg_path: Path, png_path: Path) -> None:
if not shutil.which("rsvg-convert"):
raise RuntimeError("rsvg-convert not found (install librsvg / gnome-base/rsvg)")
subprocess.run(
[
"rsvg-convert",
"-d", str(RSVG_DPI),
"-p", str(RSVG_DPI),
"-o", str(png_path),
str(svg_path),
],
check=True,
timeout=60,
)
def diagram_size_cm(png_path: Path, max_width_cm: float) -> tuple[float, float]:
"""Width/height in cm preserving aspect ratio, capped by page frame."""
w_px, h_px = ImageReader(str(png_path)).getSize()
max_w = max_width_cm * cm
max_h = MAX_DIAGRAM_HEIGHT_CM * cm
scale = min(max_w / w_px, max_h / h_px)
return w_px * scale, h_px * scale
def p(text: str, style) -> Paragraph: def p(text: str, style) -> Paragraph:
return Paragraph(text.replace("\n", "<br/>"), style) return Paragraph(text.replace("\n", "<br/>"), style)
@@ -93,21 +126,20 @@ def add_table(story, headers: list[str], rows: list[list[str]], col_widths) -> N
story.append(t) story.append(t)
def add_diagram(story, mmd_png: str, caption: str, h2, iw: float = 16 * cm) -> None: def add_diagram(story, png_name: str, caption: str, h2, max_width_cm: float) -> None:
path = BUILD / mmd_png path = BUILD / png_name
if path.exists(): if not path.exists():
story.append(p(caption, h2)) return
story.append(Image(str(path), width=iw, height=iw * 0.55)) iw, ih = diagram_size_cm(path, max_width_cm)
story.append(Spacer(1, 0.35 * cm)) story.append(p(caption, h2))
story.append(Image(str(path), width=iw, height=ih))
story.append(Spacer(1, 0.35 * cm))
def build_pdf() -> None: def build_pdf() -> None:
styles = getSampleStyleSheet() styles = getSampleStyleSheet()
title = ParagraphStyle( title = ParagraphStyle(
"DocTitle", "DocTitle", parent=styles["Title"], fontSize=18, spaceAfter=10,
parent=styles["Title"],
fontSize=18,
spaceAfter=10,
textColor=colors.HexColor("#1565C0"), textColor=colors.HexColor("#1565C0"),
) )
h1 = ParagraphStyle("H1", parent=styles["Heading1"], fontSize=14, spaceBefore=12, spaceAfter=6) h1 = ParagraphStyle("H1", parent=styles["Heading1"], fontSize=14, spaceBefore=12, spaceAfter=6)
@@ -118,16 +150,31 @@ def build_pdf() -> None:
story: list = [] story: list = []
story.append(p("Android Cast — Git flow (green master)", title)) story.append(p("Android Cast — Git flow (green master)", title))
story.append(Spacer(1, 0.15 * cm))
add_table(
story,
["Field", "Value"],
[
["Version", DOC_VERSION],
["Published", DOC_DATE],
["Author(s)", DOC_AUTHOR],
["Markdown source", DOC_SOURCE],
["Diagrams (PDF)", f"Mermaid → SVG → PNG @ {RSVG_DPI} DPI"],
["Diagrams (vector)", "docs/_pdf_build/git_flow_diagram_*.svg"],
],
[4.0 * cm, 12.5 * cm],
)
story.append(Spacer(1, 0.25 * cm))
story.append( story.append(
p( p(
"Development cycles, branch roles, release and hotfix strategy. " "PDF diagrams are high-resolution raster with correct aspect ratio (not stretched). "
"Version 1.0 · 2026-05-21. Markdown source: docs/GIT_FLOW.md", "For infinite zoom, open the SVG files in Okular or a browser. "
"Okular: View → Zoom for PDF; SVG opens as native vector.",
small, small,
) ),
) )
story.append(Spacer(1, 0.3 * cm)) story.append(Spacer(1, 0.3 * cm))
# 1 Goals
story.append(p("1. Goals", h1)) story.append(p("1. Goals", h1))
add_table( add_table(
story, story,
@@ -142,7 +189,6 @@ def build_pdf() -> None:
) )
story.append(Spacer(1, 0.4 * cm)) story.append(Spacer(1, 0.4 * cm))
# 2 Branches
story.append(p("2. Branches", h1)) story.append(p("2. Branches", h1))
add_table( add_table(
story, story,
@@ -156,31 +202,37 @@ def build_pdf() -> None:
[3 * cm, 8.5 * cm, 4 * cm], [3 * cm, 8.5 * cm, 4 * cm],
) )
story.append(Spacer(1, 0.3 * cm)) story.append(Spacer(1, 0.3 * cm))
add_diagram(story, DIAGRAMS[0][1], DIAGRAMS[0][2], h2) add_diagram(story, DIAGRAMS[0][2], DIAGRAMS[0][3], h2, DIAGRAMS[0][6])
story.append(PageBreak()) story.append(PageBreak())
# 3 Daily dev
story.append(p("3. Daily development", h1)) story.append(p("3. Daily development", h1))
for line in [ for line in [
"1. git fetch && git checkout next && git pull", "git fetch && git checkout next && git pull",
"2. git checkout -b feature/short-description", "git checkout -b feature/short-description",
"3. Commit on topic branch; run tests before merge", "Commit on topic branch; run tests before merge",
"4. Merge topic → next (after CI green)", "Merge topic → next (after CI green)",
"5. Delete topic branch",
]: ]:
story.append(p(f"{line}", bullet)) story.append(p(f"{line}", bullet))
story.append(Spacer(1, 0.2 * cm)) story.append(Spacer(1, 0.15 * cm))
story.append(
p(
"<b>Topic branch cleanup:</b> optional locally. On the server, enable "
"<i>delete branch on merge</i> (Gitea/GitLab/GitHub) so merged feature/* "
"branches are removed automatically on origin.",
body,
),
)
story.append(Spacer(1, 0.15 * cm))
story.append( story.append(
p( p(
"<b>Agents:</b> never push directly to master or next unless the user explicitly " "<b>Agents:</b> never push directly to master or next unless the user explicitly "
"requests a release or hotfix merge.", "requests a release or hotfix merge.",
body, body,
) ),
) )
add_diagram(story, DIAGRAMS[1][1], DIAGRAMS[1][2], h2, iw=15 * cm) add_diagram(story, DIAGRAMS[1][2], DIAGRAMS[1][3], h2, DIAGRAMS[1][6])
story.append(PageBreak()) story.append(PageBreak())
# 4 Release
story.append(p("4. Release (next → master)", h1)) story.append(p("4. Release (next → master)", h1))
for line in [ for line in [
"Confirm next is QA-ready and CI green", "Confirm next is QA-ready and CI green",
@@ -191,10 +243,9 @@ def build_pdf() -> None:
"Continue feature branches from updated next", "Continue feature branches from updated next",
]: ]:
story.append(p(f"{line}", bullet)) story.append(p(f"{line}", bullet))
add_diagram(story, DIAGRAMS[2][1], DIAGRAMS[2][2], h2, iw=15 * cm) add_diagram(story, DIAGRAMS[2][2], DIAGRAMS[2][3], h2, DIAGRAMS[2][6])
story.append(PageBreak()) story.append(PageBreak())
# 5 Hotfix
story.append(p("5. Hotfixes — recommended strategy", h1)) story.append(p("5. Hotfixes — recommended strategy", h1))
story.append( story.append(
p( p(
@@ -202,7 +253,7 @@ def build_pdf() -> None:
"released code. After fix and tests: merge → master, tag patch (e.g. v0.1.3), then " "released code. After fix and tests: merge → master, tag patch (e.g. v0.1.3), then "
"<b>merge master → next</b> so integration does not miss the fix.", "<b>merge master → next</b> so integration does not miss the fix.",
body, body,
) ),
) )
story.append(Spacer(1, 0.15 * cm)) story.append(Spacer(1, 0.15 * cm))
story.append( story.append(
@@ -210,7 +261,7 @@ def build_pdf() -> None:
"<b>Exception (option 2):</b> bug exists only on next — branch fix/* from next, " "<b>Exception (option 2):</b> bug exists only on next — branch fix/* from next, "
"merge to next only; ships with the next normal release.", "merge to next only; ships with the next normal release.",
body, body,
) ),
) )
story.append(Spacer(1, 0.15 * cm)) story.append(Spacer(1, 0.15 * cm))
story.append( story.append(
@@ -218,9 +269,9 @@ def build_pdf() -> None:
"<b>Why not hotfix only on next?</b> next often contains unreleased features; " "<b>Why not hotfix only on next?</b> next often contains unreleased features; "
"merging next to master for a patch would ship extra code or block the fix.", "merging next to master for a patch would ship extra code or block the fix.",
body, body,
) ),
) )
add_diagram(story, DIAGRAMS[3][1], DIAGRAMS[3][2], h2, iw=14 * cm) add_diagram(story, DIAGRAMS[3][2], DIAGRAMS[3][3], h2, DIAGRAMS[3][6])
story.append(p("5.1 Sync cheat sheet", h2)) story.append(p("5.1 Sync cheat sheet", h2))
add_table( add_table(
@@ -236,7 +287,6 @@ def build_pdf() -> None:
) )
story.append(PageBreak()) story.append(PageBreak())
# 6 Protection + agents
story.append(p("6. Branch protection and CI", h1)) story.append(p("6. Branch protection and CI", h1))
add_table( add_table(
story, story,
@@ -245,10 +295,21 @@ def build_pdf() -> None:
["CI required before merge", "yes", "yes"], ["CI required before merge", "yes", "yes"],
["No WIP direct push", "yes", "yes"], ["No WIP direct push", "yes", "yes"],
["Annotated release tags", "yes", "no"], ["Annotated release tags", "yes", "no"],
["Force-push", "forbidden", "forbidden"], ["Force-push", "never", "maintainer only, rare"],
["Delete branch on merge", "n/a", "yes (topic branches)"],
], ],
[5.5 * cm, 5 * cm, 5 * cm], [5.5 * cm, 5 * cm, 5 * cm],
) )
story.append(Spacer(1, 0.2 * cm))
story.append(
p(
"<b>Force-push on next:</b> keep disabled by default. Allow only when you intentionally "
"rewrite integration history (e.g. squash fixups before release) — never on master. "
"Prefer merge/rebase on topic branches instead. If you force-push next, coordinate with "
"anyone else using the repo and re-sync their clones.",
body,
),
)
story.append(Spacer(1, 0.35 * cm)) story.append(Spacer(1, 0.35 * cm))
story.append(p("7. Cursor / agent checklist", h1)) story.append(p("7. Cursor / agent checklist", h1))
for line in [ for line in [
@@ -256,17 +317,15 @@ def build_pdf() -> None:
"Run tests before recommending merge to next", "Run tests before recommending merge to next",
"Releases and tags only with explicit user approval", "Releases and tags only with explicit user approval",
"Hotfix released code from master; sync master into next after tag", "Hotfix released code from master; sync master into next after tag",
"Never force-push master or next", "Never force-push master; avoid force-push on next unless user requests",
]: ]:
story.append(p(f"{line}", bullet)) story.append(p(f"{line}", bullet))
story.append(Spacer(1, 0.4 * cm)) story.append(Spacer(1, 0.4 * cm))
story.append( story.append(
p( p(
"Full narrative and Mermaid sources: docs/GIT_FLOW.md, " "Regenerate: python3 docs/_pdf_build/build_git_flow_pdf.py",
"docs/_pdf_build/git_flow_diagram_*.mmd · "
"Regenerate PDF: python3 docs/_pdf_build/build_git_flow_pdf.py",
small, small,
) ),
) )
doc = SimpleDocTemplate( doc = SimpleDocTemplate(
@@ -284,12 +343,17 @@ def build_pdf() -> None:
def main() -> int: def main() -> int:
try: try:
render_mermaid() render_mermaid()
for _mmd, svg_name, png_name, *_ in DIAGRAMS:
svg_to_png(BUILD / svg_name, BUILD / png_name)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print("Mermaid render failed:", e, file=sys.stderr) print("Diagram render failed:", e, file=sys.stderr)
return 1 return 1
except FileNotFoundError as e: except FileNotFoundError as e:
print(e, file=sys.stderr) print(e, file=sys.stderr)
return 1 return 1
except RuntimeError as e:
print(e, file=sys.stderr)
return 1
build_pdf() build_pdf()
return 0 return 0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -11,5 +11,4 @@ sequenceDiagram
Dev->>CI: run tests Dev->>CI: run tests
CI-->>Dev: pass CI-->>Dev: pass
Dev->>Next: merge topic branch Dev->>Next: merge topic branch
Note over Next: next stays integration line Note over Next: server may delete merged branch on origin
Dev->>Topic: delete branch

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 149 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 50 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,3 @@
reportlab>=4.0
# PDF diagrams: Mermaid CLI (npx) + rsvg-convert (system package librsvg).