From 2135204d7c91c7ff523922648e8bd84b4539d3ec Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Mon, 13 Jul 2026 22:45:51 +0300 Subject: [PATCH] docs(planning): compact planning/, promote stranded truth to architecture/ Remove redundant artifacts from planning/ and move live facts into the truth home, without rewriting the rationale the convention keeps. - architecture/org-profile.md: record the DI table's alphabetical ordering rule (modern-di first, that-depends last) and the pre-PyPI Downloads-badge lag. Both were live but written down nowhere. - architecture/community.md (new): what is actually true today, i.e. Discussions on three repos and deliberately no chat rooms. - 2026-07-02.04-aiohttp-surfaces: retire the stale "ready-to-fire" framing (every precondition is met) and drop the embedded execution plan, which convention 2.0.0 bans from changes/. Kept as the why. - deferred.md: track the genuinely pending work with revisit triggers, i.e. the coordinated launch, the launch-week chat rooms, and the borrowed-audience program. - Replace verbatim copies of shipped source (apparel_back, render_apparel, boosty_cover, _quantize_png, async_loop) and the org mark's coordinate and palette dumps with pointers to the code that owns them, keeping the design rationale. - Tick the verification boxes on the two merged changes, after re-verifying each claim. - Fix a broken changes/ link left by the 2.0 folder-to-file flatten. docs-lockup-hero keeps its CSS: it lives in other repos' brand.css, so this file is the only copy here. just check-planning: OK. just test: 149 passed. --- architecture/community.md | 38 ++++++ architecture/org-profile.md | 12 ++ .../2026-06-24.01-promotion-strategy.md | 16 +-- .../2026-06-27.01-community-channels.md | 16 ++- .../2026-06-28.01-org-favicon-social-card.md | 77 ++++-------- .../changes/2026-06-30.03-png-optimization.md | 24 +--- .../changes/2026-07-02.02-aiohttp-brand.md | 19 +-- .../changes/2026-07-02.04-aiohttp-surfaces.md | 119 +++--------------- planning/changes/2026-07-04.01-org-tshirt.md | 32 +---- .../changes/2026-07-07.01-boosty-cover.md | 17 +-- ...026-07-13.02-metadata-consistency-sweep.md | 8 +- .../2026-07-13.03-drop-template-type-badge.md | 6 +- planning/deferred.md | 54 ++++++++ 13 files changed, 184 insertions(+), 254 deletions(-) create mode 100644 architecture/community.md diff --git a/architecture/community.md b/architecture/community.md new file mode 100644 index 0000000..1b12b8e --- /dev/null +++ b/architecture/community.md @@ -0,0 +1,38 @@ +# Community channels + +How the org reaches, supports, and retains users. Living prose, no frontmatter; +dated by git. + +## What exists today + +**GitHub Discussions is the org's only owned channel**, and its primary support +and feedback surface. It is enabled on `modern-di`, `that-depends`, and `.github` +— not yet org-wide. There is deliberately **no Discord and no Telegram room**: +see `planning/deferred.md`, which holds the plan to open them and the reason they +are not open yet. + +## Why Discussions, and why nothing else yet + +Discussions is searchable, Google-indexed, and async, which suits a solo +maintainer in one timezone. The durable-record argument is the load-bearing one: a +chat answer is ephemeral, while a Discussions answer compounds — it also serves +everyone who later searches the same error. + +The standing principle is **borrow audiences before building our own**. A young +project's scarcest resource is the attention needed to seed a room, so the org +participates where Python users already are rather than standing up rooms it +cannot fill. An empty room is worse than no room: a visitor who arrives at a +"3 members, last message 2 weeks ago" tab gets a worse signal than from a clean +Discussions tab. + +## Routing rule + +When chat rooms do exist, *answerable* questions get pushed to Discussions +("ask in Discussions so the next person finds it; chat for everything else"), so +the indexed record keeps growing and chat stays low-stakes. The same reflex +applies to any question that arrives by another route today. + +## Scope + +One org-wide room per platform if and when rooms open — never per-repo. The whole +stack shares one community. diff --git a/architecture/org-profile.md b/architecture/org-profile.md index c2ee423..aeba3a3 100644 --- a/architecture/org-profile.md +++ b/architecture/org-profile.md @@ -22,11 +22,23 @@ frontmatter; dated by git. `Project | What it is | Stars | Downloads`; the templates table is `Project | What it is | Stars`. +## Row ordering + +Within the **Dependency injection** table, integrations sort **alphabetically**, +with two fixed anchors: `modern-di` (the core) stays first and `that-depends` +(the earlier framework) stays last. The same order holds across the docs-site DI +list and `projects.py::MANIFEST`. A new integration therefore slots into its +alphabetical position with no insertion-point decision to make. + ## Badge rules - **Published libraries** get a Stars shield → stargazers, and a monthly Downloads shield (`static.pepy.tech/badge//month`) → the pepy project page. +- A repo may be listed **before its package reaches PyPI**. Its Downloads badge + 404s until the package is published and pepy indexes it; the Stars badge and + repo link resolve meanwhile. This lag is self-healing — note it, don't block + the listing on it. - **Templates** (`*-sqlalchemy-template`, not on PyPI) get a Stars shield only — they have no Downloads column, and their table stops after Stars rather than carrying a filler chip. diff --git a/planning/changes/2026-06-24.01-promotion-strategy.md b/planning/changes/2026-06-24.01-promotion-strategy.md index 115d94b..6031a6e 100644 --- a/planning/changes/2026-06-24.01-promotion-strategy.md +++ b/planning/changes/2026-06-24.01-promotion-strategy.md @@ -145,12 +145,12 @@ abandoned. ### Phase 4 — Coordinated launch *(fires only after Phases 0–2 are done)* -- One spike: **"Show HN: modern-di"** + r/Python + Lobsters, plus an org-story - announcement post ("the modern Python stack"). Claude drafts all posts; maintainer - picks the window and publishes. awesome-list PRs land in the same window. - Rationale: launch channels are effectively one-shot. Launching onto polished, conversion-ready assets is the difference between a spike that compounds and one that evaporates. +- **Not yet fired.** The posts, platforms, and timing are written up in + [`launch-playbook.md`](../launch-playbook.md) and tracked with their revisit + trigger in [`deferred.md`](../deferred.md) — not restated here. ### Phase 5 — Sustain + feedback loop *(ongoing)* @@ -198,9 +198,9 @@ implementation plan must account for per-repo clone/branch/PR flow. - Standing up Discord/Telegram up front (revisit in Phase 5 if warranted). - Paid promotion / ads. -## Open questions (resolve during planning) +## Open questions -- Exact `modern-di` differentiators vs competitors — pending Phase 1 research + - maintainer sign-off on the comparison table. -- Whether benchmarks are a credible differentiator worth a dedicated page. -- Launch window timing (maintainer's call). +Resolved since: the `modern-di` differentiators and the comparison table shipped +with the Phase 1 docs. Still open and carried into [`deferred.md`](../deferred.md): +the launch window (maintainer's call), and whether benchmarks are a credible +differentiator worth a dedicated page. diff --git a/planning/changes/2026-06-27.01-community-channels.md b/planning/changes/2026-06-27.01-community-channels.md index 5ad493d..4999fed 100644 --- a/planning/changes/2026-06-27.01-community-channels.md +++ b/planning/changes/2026-06-27.01-community-channels.md @@ -6,9 +6,14 @@ summary: Which channels the modern-python org uses to reach, support, and retain **Scope:** Which channels the `modern-python` org uses to reach, support, and retain users — and how they fit together. Extends the -[promotion strategy](../2026-06-24.01-promotion-strategy/design.md); **amends its Phase 5 +[promotion strategy](2026-06-24.01-promotion-strategy.md); **amends its Phase 5 deferral of Discord/Telegram.** +> **Where this landed.** What is true *today* — Discussions only, no chat rooms — +> is in [`architecture/community.md`](../../architecture/community.md). The rooms +> and the borrowed-audience program are not built yet and are tracked, with their +> triggers, in [`deferred.md`](../deferred.md). This file stays as the *why*. + ## Why this doc exists The promotion strategy and launch playbook already specify the **broadcast** @@ -140,9 +145,8 @@ against: - Paid promotion / ads. - Standing up the rooms *before* launch week. -## Open questions (resolve during planning) +## Open questions -- Exact launch-week sequencing of room creation vs. the Show HN spike (maintainer's - call on the window). -- Which mod/anti-spam bots to use for Discord and Telegram. -- Whether to retire one room if it stays inactive, and the threshold for that call. \ No newline at end of file +Carried forward with the deferred work, not resolved here: launch-week sequencing +of room creation vs. the Show HN spike, which mod/anti-spam bots to use, and the +inactivity threshold for retiring a room. See [`deferred.md`](../deferred.md). \ No newline at end of file diff --git a/planning/changes/2026-06-28.01-org-favicon-social-card.md b/planning/changes/2026-06-28.01-org-favicon-social-card.md index 80a866c..97d5f8e 100644 --- a/planning/changes/2026-06-28.01-org-favicon-social-card.md +++ b/planning/changes/2026-06-28.01-org-favicon-social-card.md @@ -28,67 +28,36 @@ two Python snakes abstracted. Two forms: diagonal-cut tail) framing the stacked wordmark `MODERN` / `PYTHON`. Used for the social card and square. -### Palette (concrete) +### Palette -| Token | Hex | Role | -|-------|-----|------| -| Green (ink, on light) | `#356852` | snakes/text on cream | -| Green (surface) | `#2f5e4a` | green-background fills | -| Gold (on light) | `#c98a00` | accent on cream | -| Gold (on dark) | `#f0b528` | accent on green | -| Cream | `#f4f1e8` | light surface; **and** the light "ink" on green (not pure white) | - -Two-color pairing: the **top-left** snake + `MODERN` use the green/cream -"structure" color; the **bottom-right** snake + `PYTHON` use gold. On cream the -structure color is green `#356852`; on green it is cream `#f4f1e8`. +Five tokens (green ink / green surface / gold-light / gold-dark / cream), now +authoritative in `brand/build/tokens.py`. The two-color pairing is the load-bearing +idea: the **top-left** snake + `MODERN` carry the "structure" color, the +**bottom-right** snake + `PYTHON` carry gold — and "structure" resolves to green on +cream, cream on green. That `struct`/`gold` role split is what every geometry +function takes as parameters. ### Typeface -**Jost** (SIL OFL — free for commercial use, embedding, and outlining), weight -400. Wordmark glyphs are **outlined to SVG paths** at build time so assets -render without the font installed (same approach the repo already uses for -JetBrains Mono). Jost + its OFL license are vendored under `brand/build/fonts/`. +**Jost** (SIL OFL — free for commercial use, embedding, and outlining), weight 400, +vendored with its license under `brand/build/fonts/`. Wordmark glyphs are **outlined +to SVG paths** at build time so assets render without the font installed. See +`architecture/site-branding.md`. --- -## Geometry (authoritative coordinates) - -### Icon (100×100 viewBox) — favicon / apple-touch / avatar - -One mark (`_icon_mark`), used full-bleed by `icon()` and padded by `icon_circle()`. -Snakes reach the borders; each has a square **block head** at one end and a -**diagonal-cut tail** (outer edge straight, inner edge sliced) at the other. Tail -polygon bases overlap the stroke by ~2px so tail and body render as one shape. -``` -bg: rect 0 0 100 100 fill BG (square, no rx) -snake TL: path "M15 68 L15 15 L68 15" stroke STRUCT width 11 (butt, miter) -head TL: rect x61 y8 w14 h14 rx2 fill STRUCT -tail TL: polygon "9.5,66 20.5,66 20.5,68 9.5,79" fill STRUCT -snake BR: path "M85 32 L85 85 L32 85" stroke GOLD width 11 -head BR: rect x25 y78 w14 h14 rx2 fill GOLD -tail BR: polygon "90.5,34 79.5,34 79.5,32 90.5,21" fill GOLD -chevron: polyline "45,40 57,50 45,60" stroke GOLD width 6 (round) -``` -`icon_circle` wraps the mark in `translate(50,50) scale(0.74) translate(-50,-50)` -so it fits inside the inscribed circle with margin. -On green: BG `#2f5e4a`, STRUCT (top-left snake) cream `#f4f1e8`, GOLD (bottom-right -snake + chevron) `#f0b528`. - -### Wordmark lockup (540×250 viewBox — the unit scaled into cards) - -``` -snake TL: path "M138 122 L138 50 L210 50" stroke STRUCT width 8 (butt, miter) -snake BR: path "M402 128 L402 200 L330 200" stroke GOLD width 8 -head TL: rect x202.5 y42.5 w15 h15 rx3 fill STRUCT -head BR: rect x322.5 y192.5 w15 h15 rx3 fill GOLD -tail TL: polygon "134,120 142,120 142,122 134,130" fill STRUCT -tail BR: polygon "406,130 398,130 398,128 406,120" fill GOLD -MODERN: x270 y126 Jost 400 size 50 fill STRUCT text-anchor middle textLength 210 -PYTHON: x270 y166 Jost 400 size 50 fill GOLD text-anchor middle textLength 210 -``` -`textLength 210` + `lengthAdjust="spacingAndGlyphs"` pins each line's width so -the crop marks frame the text exactly regardless of renderer. Baselines 126/166 -= **gap 40**. +## Geometry + +The coordinates are generated by `brand/build/geometry.py` — `_icon_mark` (the +100×100 icon), `icon()` / `icon_circle()` (full-bleed and padded-for-circular-crop +variants), and `lockup_body()` (the 540×250 wordmark unit scaled into the cards) — +and are pinned by `tests/test_geometry.py`. The code is the authority; the two +constraints worth knowing *why* about: + +- **Tail polygons overlap the stroke by ~2px** so each snake's tail and body + render as one continuous shape rather than showing a seam. +- **`textLength` + `lengthAdjust="spacingAndGlyphs"`** pins each wordmark line's + width, so the crop marks frame the text identically regardless of renderer. --- diff --git a/planning/changes/2026-06-30.03-png-optimization.md b/planning/changes/2026-06-30.03-png-optimization.md index 9b574c1..f61de1b 100644 --- a/planning/changes/2026-06-30.03-png-optimization.md +++ b/planning/changes/2026-06-30.03-png-optimization.md @@ -48,24 +48,12 @@ value is a smaller repo and smaller cards copied into the 7 docs repos ### 1. Quantization in `raster.py` `export_png` currently shells out to `rsvg-convert` and returns `bool`. Add a -post-write step that re-saves the PNG palette-quantized: - -```python -_PNG_COLORS = 32 # palette size; flat art needs few. 8 already looked clean; 32 = headroom. - - -def _quantize_png(path: Path, colors: int = _PNG_COLORS) -> None: - """Re-save a PNG as an indexed-palette image (visually lossless for flat art). - FASTOCTREE preserves alpha, so it is correct for both the opaque social cards - and the transparent marks. No-op (leaves the RGBA file) if Pillow is absent.""" - try: - from PIL import Image - except ModuleNotFoundError: - return - im = Image.open(path).convert("RGBA") - q = im.quantize(colors=colors, method=Image.Quantize.FASTOCTREE) - q.save(path, format="PNG", optimize=True) -``` +post-write step that re-saves the PNG palette-quantized — shipped as +`raster.py::_quantize_png` (Pillow `FASTOCTREE`, `_PNG_COLORS` palette), which is +the authority. Two choices worth the *why*: **FASTOCTREE** because it preserves +alpha, so the one function is correct for both the opaque social cards and the +transparent marks; and the quantize step **no-ops if Pillow is absent** rather +than failing the build. `export_png` calls `_quantize_png(png_path)` after a successful `rsvg-convert`. If `rsvg-convert` is absent there's no PNG and nothing to quantize (unchanged diff --git a/planning/changes/2026-07-02.02-aiohttp-brand.md b/planning/changes/2026-07-02.02-aiohttp-brand.md index 2d1f937..30d8180 100644 --- a/planning/changes/2026-07-02.02-aiohttp-brand.md +++ b/planning/changes/2026-07-02.02-aiohttp-brand.md @@ -49,23 +49,8 @@ substitute `{color}` for the two `{GOLD}` occurrences. This is backward compatible — the only existing caller (`db_retry`) omits the argument and keeps `GOLD`. -```python -def _circ_arc(cx: float, cy: float, rad: float, a0: float, a1: float, w: float, - color: str = GOLD) -> str: - ... # stroke and arrowhead now use {color} instead of {GOLD} - - -def async_loop(cx: float, cy: float, r: float) -> str: - """aiohttp cue: an async event-loop cycle (two chasing arrows) knocked out - of a gold disc.""" - rad = r * 0.52 - w = 3.4 - loop = ( - _circ_arc(cx, cy, rad, 25, 165, w, color=CREAM) - + _circ_arc(cx, cy, rad, 205, 345, w, color=CREAM) - ) - return f'' + loop -``` +Shipped as `symbols.py::async_loop` (with the widened `_circ_arc`); those are the +authority. `CREAM` is already imported in `symbols.py`. The mark uses only `GOLD` and `CREAM`, both in the allowed palette, so `test_only_allowed_colours` passes. diff --git a/planning/changes/2026-07-02.04-aiohttp-surfaces.md b/planning/changes/2026-07-02.04-aiohttp-surfaces.md index 639c19d..41d561a 100644 --- a/planning/changes/2026-07-02.04-aiohttp-surfaces.md +++ b/planning/changes/2026-07-02.04-aiohttp-surfaces.md @@ -1,108 +1,27 @@ --- -summary: Ready-to-fire — add modern-di-aiohttp to the profile README, docs site, and its GitHub repo settings once the repo is public. +summary: Put modern-di-aiohttp on its public surfaces — profile README row, docs-site DI listing, and GitHub repo settings — and replaced the ad-hoc insertion-point rule with alphabetical ordering of the DI integrations. --- -# Change: modern-di-aiohttp public surfaces (deferred) +# Change: modern-di-aiohttp public surfaces -## Status +## What shipped -**Partially applied.** The repo went public before the package was published to -PyPI. Progress so far: +`modern-di-aiohttp` appears on the profile README's Dependency injection table, +the docs-site DI list, and the "stack" sentence; its GitHub description, +homepage, and topics are set. The brand mark landed separately, in +`2026-07-02.02-aiohttp-brand` (PR #31). -- **GitHub repo settings — DONE.** Applied once the repo was public: description - was already `modern-di integration for aiohttp`; homepage set to - `https://modern-di.modern-python.org`; topics set to `python, - dependency-injection, di, ioc-container, modern-di, aiohttp`. Verified via - `gh repo view`. -- **Profile README row + docs-site listing — DONE (shipped ahead of PyPI).** - Maintainer chose to ship the listing now even though the package is not on - PyPI yet (`/pypi/modern-di-aiohttp/json` → 404). Consequence: the pepy - Downloads badge on the new profile row 404s until the package is published to - PyPI and pepy indexes it — same self-healing lag pattern seen for - `modern-di-starlette`. All other elements (repo link, Stars badge) resolve. -- **DI section sorted alphabetically (folded in).** Per maintainer request, the - Dependency injection integrations are now ordered alphabetically across the - profile table, the docs DI list, and the `MANIFEST` (`modern-di` stays first, - `that-depends` last). This supersedes the earlier "insert after Starlette" - placement — `modern-di-aiohttp` now lands in its alphabetical slot. Future - integrations slot in alphabetically, no insertion-point decision needed. +## Why it looks the way it does -The brand mark and assets are already done and merged (change -`2026-07-02.02-aiohttp-brand`, PR #31) — `brand/projects/modern-di-aiohttp/` -exists on `main`. +**Alphabetical ordering, decided here.** The original design inserted the row +immediately after `modern-di-starlette`. The maintainer instead ruled that the DI +integrations sort alphabetically (`modern-di` first, `that-depends` last), applied +across the profile table, the docs DI list, and `projects.py::MANIFEST`. That +supersedes the insertion-point approach entirely — future integrations slot in by +name, with no placement decision to make. The rule now lives in +[`architecture/org-profile.md`](../../architecture/org-profile.md). -## Precondition check (run first, all must pass) - -```bash -gh repo view modern-python/modern-di-aiohttp --json name # resolves -curl -s -o /dev/null -w '%{http_code}' https://pypi.org/pypi/modern-di-aiohttp/json # 200 -``` - -## Edits - -Canonical one-liner, used verbatim: **`modern-di integration for aiohttp`** -(note lowercase `aiohttp`). Insert everywhere immediately **after** -`modern-di-starlette` (order: fastapi -> litestar -> starlette -> aiohttp). - -### 1. `profile/README.md` — Dependency injection table - -Insert after the `modern-di-starlette` row. Match the current 4-column format -(`| Project | What it is | Stars | Downloads |`, post-#30 — no PyPI/Context7): - -```markdown -| [`modern-di-aiohttp`](https://github.com/modern-python/modern-di-aiohttp) | modern-di integration for aiohttp | [![Stars](https://img.shields.io/github/stars/modern-python/modern-di-aiohttp)](https://github.com/modern-python/modern-di-aiohttp/stargazers) | [![Downloads](https://static.pepy.tech/badge/modern-di-aiohttp/month)](https://pepy.tech/projects/modern-di-aiohttp) | -``` - -### 2. `docs/index.md` — DI list (`## Dependency injection { #di }`) - -Insert after the `modern-di-starlette` bullet: - -```markdown -- [`modern-di-aiohttp`](https://github.com/modern-python/modern-di-aiohttp) — `modern-di` integration for aiohttp. -``` - -### 3. `docs/index.md` — "The stack" sentence - -Add `aiohttp,` after `Starlette,`. Replace: - -``` - dependency injection with one wiring shared across FastAPI, Litestar, - Starlette, FastStream, and Typer. -``` - -with: - -``` - dependency injection with one wiring shared across FastAPI, Litestar, - Starlette, aiohttp, FastStream, and Typer. -``` - -### 4. GitHub repo settings (read-then-diff, apply only what's missing) - -```bash -gh repo view modern-python/modern-di-aiohttp --json description,homepageUrl,repositoryTopics -gh repo edit modern-python/modern-di-aiohttp \ - --description "modern-di integration for aiohttp" \ - --homepage "https://modern-di.modern-python.org" \ - --add-topic python --add-topic dependency-injection --add-topic di \ - --add-topic ioc-container --add-topic modern-di --add-topic aiohttp -``` - -## Verification (post-publish) - -- `just check-planning` -> `planning: OK`; `just test` green (no code touched, - but run the gate). -- `grep -c modern-di-aiohttp profile/README.md` -> 1; `... docs/index.md` -> 1 - (the DI bullet; the stack sentence names "aiohttp", not the slug). -- Badge/link resolve: `curl -sI` the Stars + Downloads badge URLs and the repo - link return 200 (Downloads may lag until pepy indexes the new package — the - same self-healing lag seen for modern-di-starlette; note it, do not block). -- `gh repo view` reflects the intended description, homepage, and topics. -- The `modern-di-aiohttp` README's brand images already resolve (assets on - `main` since #31). - -## Notes - -Ship as one PR (this change finalized + the two file edits) once unblocked; the -GitHub-settings step is out-of-repo and applied alongside. Independent of any -other pending work. +**Listed ahead of PyPI.** The repo went public before the package was published, +and the maintainer chose to list it anyway. The consequence is a 404ing pepy +Downloads badge until the package ships and pepy indexes it — the same +self-healing lag already seen with `modern-di-starlette`. Accepted, not blocked on. diff --git a/planning/changes/2026-07-04.01-org-tshirt.md b/planning/changes/2026-07-04.01-org-tshirt.md index 1985779..2014f02 100644 --- a/planning/changes/2026-07-04.01-org-tshirt.md +++ b/planning/changes/2026-07-04.01-org-tshirt.md @@ -48,22 +48,7 @@ MODERN/PYTHON crop-mark lockup, drawn in the 540×250 space) and `outline_text() font at serve time). The viewBox extends the wordmark's `118 32 304 184` down to fit the URL line, keeping the same horizontal center (x=270). -```python -def apparel_back(*, struct: str, gold: str) -> str: - """Back-of-shirt lockup: the MODERN/PYTHON wordmark with the full domain - outlined beneath it, transparent, for the cream+gold-dark colorway.""" - url, _ = outline_text( - "modern-python.org", 18, x=270, baseline_y=240, - anchor="middle", color=gold, letter_spacing=3, - ) - return ( - '' - + lockup_body(struct=struct, gold=gold) - + url - + "" - ) -``` +Shipped as `geometry.apparel_back`; that function is the authority. The URL is set in `gold` (gold-dark) — the wordmark supplies `struct` (cream) for MODERN and gold for PYTHON, so the graphic stays within the two allowed inks. The @@ -75,18 +60,9 @@ The chest art needs no new geometry — it is the existing ### 2. Apparel render module — `brand/build/apparel.py` -New module mirroring `projects.py`'s shape, exposing `render_apparel()`. Writes -to `brand/apparel/`: - -```python -def render_apparel() -> None: - APPAREL.mkdir(parents=True, exist_ok=True) - ink = dict(struct=t.CREAM, gold=t.GOLD_DARK) - _write(APPAREL / "chest-mark.svg", g.mark(**ink)) - export_png(APPAREL / "chest-mark.svg", APPAREL / "chest-mark-1050.png", width=1050) - _write(APPAREL / "back-lockup.svg", g.apparel_back(**ink)) - export_png(APPAREL / "back-lockup.svg", APPAREL / "back-lockup-2400.png", width=2400) -``` +New module mirroring `projects.py`'s shape, exposing `render_apparel()`, which +writes the chest mark and back lockup (SVG + PNG) into `brand/apparel/` in the +cream/gold-dark ink pair. Shipped as `brand/build/apparel.py::render_apparel`. PNG widths are the print sizes at 300 DPI: chest 3.5 in → 1050 px (→ 1050×1050); back 8 in → 2400 px (→ 2400×1800, aspect preserved by `rsvg-convert -w`). PNGs diff --git a/planning/changes/2026-07-07.01-boosty-cover.md b/planning/changes/2026-07-07.01-boosty-cover.md index f5891ee..fc20ca3 100644 --- a/planning/changes/2026-07-07.01-boosty-cover.md +++ b/planning/changes/2026-07-07.01-boosty-cover.md @@ -50,22 +50,7 @@ crop-mark lockup in the 540×250 space); the lockup's box center (270, 125) is placed on the banner center (960, 120), so `translate` resolves to `translate(663.0,-17.5) scale(1.1)`. -```python -def boosty_cover(*, bg: str, struct: str, gold: str) -> str: - """Boosty profile-header banner — 8:1 (1920x240). Just the MODERN/PYTHON - lockup, centered on a full-bleed field; no tagline.""" - w, h = 1920, 240 - s = 1.1 # lockup box is 540x250 (visual y-center 125) - tx = round(w / 2 - 270 * s, 1) # box-center horizontally - ty = round(h / 2 - 125 * s, 1) # box-center vertically - body = lockup_body(struct=struct, gold=gold) - return ( - _SVG_OPEN.format(w=w, h=h) - + f'' - + f'{body}' - + "" - ) -``` +Shipped as `geometry.boosty_cover`; that function is the authority. MODERN is `struct` (cream) and PYTHON is `gold` (gold-dark), so the banner stays within the on-green two-ink treatment. There is no tagline text, so `outline_text` diff --git a/planning/changes/2026-07-13.02-metadata-consistency-sweep.md b/planning/changes/2026-07-13.02-metadata-consistency-sweep.md index 1dfe864..2fe0361 100644 --- a/planning/changes/2026-07-13.02-metadata-consistency-sweep.md +++ b/planning/changes/2026-07-13.02-metadata-consistency-sweep.md @@ -47,9 +47,9 @@ makes that claim true, so the doc needs no edit. ## Verification -- [ ] Every rewritten profile blurb equals the repo's GitHub description +- [x] Every rewritten profile blurb equals the repo's GitHub description character-for-character (`gh repo view` comparison). -- [ ] All 12 canonical strings are ≤120 chars, carry no trailing period, and +- [x] All 12 canonical strings are ≤120 chars, carry no trailing period, and contain no `|` (would break the table). -- [ ] `just check-planning` — clean. -- [ ] Both template `pyproject.toml` descriptions match their GitHub description. +- [x] `just check-planning` — clean. +- [x] Both template `pyproject.toml` descriptions match their GitHub description. diff --git a/planning/changes/2026-07-13.03-drop-template-type-badge.md b/planning/changes/2026-07-13.03-drop-template-type-badge.md index 2506f50..1f3fee6 100644 --- a/planning/changes/2026-07-13.03-drop-template-type-badge.md +++ b/planning/changes/2026-07-13.03-drop-template-type-badge.md @@ -30,7 +30,7 @@ both the header name and the chip. ## Verification -- [ ] `just check-planning` — planning index clean. -- [ ] `just test` — full suite green. -- [ ] Templates table renders with three columns; no `type-template` shield URL +- [x] `just check-planning` — planning index clean. +- [x] `just test` — full suite green. +- [x] Templates table renders with three columns; no `type-template` shield URL remains anywhere in the repo. diff --git a/planning/deferred.md b/planning/deferred.md index 563a9de..1922178 100644 --- a/planning/deferred.md +++ b/planning/deferred.md @@ -2,6 +2,60 @@ Real-but-unscheduled items, each with a revisit trigger. Add entries lazily. +## Coordinated launch (promotion strategy, Phase 4) + +**What:** Fire the staggered launch — `modern-di` (Show HN + r/Python) → the +org-story announcement → `faststream-outbox` → `httpware`, roughly one per week. +The ready-to-post copy, the per-platform formats, and the timing/etiquette rules +are already written and kept in [`launch-playbook.md`](launch-playbook.md); the +rationale is in [`changes/2026-06-24.01-promotion-strategy.md`](changes/2026-06-24.01-promotion-strategy.md). + +**Why deferred:** Phase 4 fires only once Phases 0–2 (foundation hygiene, +positioning backbone, conversion polish) are complete. Much of that has since +shipped — community-health defaults, the brand kit, the profile tables, the docs +sites — so this is close to ready. + +**Revisit trigger:** when the maintainer judges the positioning surfaces done and +picks a launch window. Launching is a one-shot per project (greenfield: no prior +HN/Reddit/Lobsters threads exist for these projects), so it is worth not wasting. + +## Community chat rooms (Telegram + Discord) + +**What:** Open one org-wide Telegram group (Russian-speaking Python community) +and one Discord server (English/global), each with a mod/anti-spam bot, pinned +rules, and a pinned honest-status message. Link both from every README, docs +site, and the org profile, with a standing reflex to redirect *answerable* +questions to Discussions. Rationale and the two-room justification (a language/geo +split, the only rationale that keeps two rooms coherent rather than fragmenting a +small community) are in +[`changes/2026-06-27.01-community-channels.md`](changes/2026-06-27.01-community-channels.md). +Present-day truth — Discussions only, no rooms — is in `architecture/community.md`. + +**Why deferred:** deliberately timed. The rooms open **during launch week, not +before**: an empty room opened months ahead actively hurts, because at the +highest-traffic moment every visitor sees "3 members, last message 2 weeks ago". +Launch traffic is what seeds them past the empty-room threshold. + +**Revisit trigger:** the launch window above. Open the rooms as it starts. If a +room stays inactive 30 days post-launch, fold it back into Discussions rather +than keeping a dead room linked. + +## Borrowed-audience channel program + +**What:** Ongoing, no rooms to run: participate in the official Python Discord and +the FastAPI/Litestar/FastStream community channels; submit to **PyCoder's Weekly** +and **Python Weekly**; pitch **Talk Python to Me** / **Python Bytes** once docs are +polished; cross-post the org-story piece to dev.to / Hashnode with a canonical +link back; answer real DI/outbox questions on Stack Overflow. Ranked by ROI in +[`changes/2026-06-27.01-community-channels.md`](changes/2026-06-27.01-community-channels.md). + +**Why deferred:** highest-ROI gap for a young project, but unscheduled — it is a +standing program rather than a task, and the newsletter/podcast pitches land best +alongside the launch. + +**Revisit trigger:** newsletters and cross-posts at launch; podcasts once the docs +are polished enough to withstand the traffic. + ## Zensical cutover for the docs sites **What:** Replace Material for MkDocs with [Zensical](https://zensical.org)