Skip to content

Commit 2d1e5b5

Browse files
authored
docs(planning): compact planning/, promote stranded truth to architecture/ (#45)
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.
1 parent a8e491c commit 2d1e5b5

13 files changed

Lines changed: 184 additions & 254 deletions

architecture/community.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Community channels
2+
3+
How the org reaches, supports, and retains users. Living prose, no frontmatter;
4+
dated by git.
5+
6+
## What exists today
7+
8+
**GitHub Discussions is the org's only owned channel**, and its primary support
9+
and feedback surface. It is enabled on `modern-di`, `that-depends`, and `.github`
10+
— not yet org-wide. There is deliberately **no Discord and no Telegram room**:
11+
see `planning/deferred.md`, which holds the plan to open them and the reason they
12+
are not open yet.
13+
14+
## Why Discussions, and why nothing else yet
15+
16+
Discussions is searchable, Google-indexed, and async, which suits a solo
17+
maintainer in one timezone. The durable-record argument is the load-bearing one: a
18+
chat answer is ephemeral, while a Discussions answer compounds — it also serves
19+
everyone who later searches the same error.
20+
21+
The standing principle is **borrow audiences before building our own**. A young
22+
project's scarcest resource is the attention needed to seed a room, so the org
23+
participates where Python users already are rather than standing up rooms it
24+
cannot fill. An empty room is worse than no room: a visitor who arrives at a
25+
"3 members, last message 2 weeks ago" tab gets a worse signal than from a clean
26+
Discussions tab.
27+
28+
## Routing rule
29+
30+
When chat rooms do exist, *answerable* questions get pushed to Discussions
31+
("ask in Discussions so the next person finds it; chat for everything else"), so
32+
the indexed record keeps growing and chat stays low-stakes. The same reflex
33+
applies to any question that arrives by another route today.
34+
35+
## Scope
36+
37+
One org-wide room per platform if and when rooms open — never per-repo. The whole
38+
stack shares one community.

architecture/org-profile.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,23 @@ frontmatter; dated by git.
2222
`Project | What it is | Stars | Downloads`; the templates table is
2323
`Project | What it is | Stars`.
2424

25+
## Row ordering
26+
27+
Within the **Dependency injection** table, integrations sort **alphabetically**,
28+
with two fixed anchors: `modern-di` (the core) stays first and `that-depends`
29+
(the earlier framework) stays last. The same order holds across the docs-site DI
30+
list and `projects.py::MANIFEST`. A new integration therefore slots into its
31+
alphabetical position with no insertion-point decision to make.
32+
2533
## Badge rules
2634

2735
- **Published libraries** get a Stars shield → stargazers, and a monthly
2836
Downloads shield (`static.pepy.tech/badge/<pkg>/month`) → the pepy project
2937
page.
38+
- A repo may be listed **before its package reaches PyPI**. Its Downloads badge
39+
404s until the package is published and pepy indexes it; the Stars badge and
40+
repo link resolve meanwhile. This lag is self-healing — note it, don't block
41+
the listing on it.
3042
- **Templates** (`*-sqlalchemy-template`, not on PyPI) get a Stars shield only —
3143
they have no Downloads column, and their table stops after Stars rather than
3244
carrying a filler chip.

planning/changes/2026-06-24.01-promotion-strategy.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,12 @@ abandoned.
145145

146146
### Phase 4 — Coordinated launch *(fires only after Phases 0–2 are done)*
147147

148-
- One spike: **"Show HN: modern-di"** + r/Python + Lobsters, plus an org-story
149-
announcement post ("the modern Python stack"). Claude drafts all posts; maintainer
150-
picks the window and publishes. awesome-list PRs land in the same window.
151148
- Rationale: launch channels are effectively one-shot. Launching onto polished,
152149
conversion-ready assets is the difference between a spike that compounds and one
153150
that evaporates.
151+
- **Not yet fired.** The posts, platforms, and timing are written up in
152+
[`launch-playbook.md`](../launch-playbook.md) and tracked with their revisit
153+
trigger in [`deferred.md`](../deferred.md) — not restated here.
154154

155155
### Phase 5 — Sustain + feedback loop *(ongoing)*
156156

@@ -198,9 +198,9 @@ implementation plan must account for per-repo clone/branch/PR flow.
198198
- Standing up Discord/Telegram up front (revisit in Phase 5 if warranted).
199199
- Paid promotion / ads.
200200

201-
## Open questions (resolve during planning)
201+
## Open questions
202202

203-
- Exact `modern-di` differentiators vs competitors — pending Phase 1 research +
204-
maintainer sign-off on the comparison table.
205-
- Whether benchmarks are a credible differentiator worth a dedicated page.
206-
- Launch window timing (maintainer's call).
203+
Resolved since: the `modern-di` differentiators and the comparison table shipped
204+
with the Phase 1 docs. Still open and carried into [`deferred.md`](../deferred.md):
205+
the launch window (maintainer's call), and whether benchmarks are a credible
206+
differentiator worth a dedicated page.

planning/changes/2026-06-27.01-community-channels.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@ summary: Which channels the modern-python org uses to reach, support, and retain
66

77
**Scope:** Which channels the `modern-python` org uses to reach, support, and
88
retain users — and how they fit together. Extends the
9-
[promotion strategy](../2026-06-24.01-promotion-strategy/design.md); **amends its Phase 5
9+
[promotion strategy](2026-06-24.01-promotion-strategy.md); **amends its Phase 5
1010
deferral of Discord/Telegram.**
1111

12+
> **Where this landed.** What is true *today* — Discussions only, no chat rooms —
13+
> is in [`architecture/community.md`](../../architecture/community.md). The rooms
14+
> and the borrowed-audience program are not built yet and are tracked, with their
15+
> triggers, in [`deferred.md`](../deferred.md). This file stays as the *why*.
16+
1217
## Why this doc exists
1318

1419
The promotion strategy and launch playbook already specify the **broadcast**
@@ -140,9 +145,8 @@ against:
140145
- Paid promotion / ads.
141146
- Standing up the rooms *before* launch week.
142147

143-
## Open questions (resolve during planning)
148+
## Open questions
144149

145-
- Exact launch-week sequencing of room creation vs. the Show HN spike (maintainer's
146-
call on the window).
147-
- Which mod/anti-spam bots to use for Discord and Telegram.
148-
- Whether to retire one room if it stays inactive, and the threshold for that call.
150+
Carried forward with the deferred work, not resolved here: launch-week sequencing
151+
of room creation vs. the Show HN spike, which mod/anti-spam bots to use, and the
152+
inactivity threshold for retiring a room. See [`deferred.md`](../deferred.md).

planning/changes/2026-06-28.01-org-favicon-social-card.md

Lines changed: 23 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -28,67 +28,36 @@ two Python snakes abstracted. Two forms:
2828
diagonal-cut tail) framing the stacked wordmark `MODERN` / `PYTHON`. Used for
2929
the social card and square.
3030

31-
### Palette (concrete)
31+
### Palette
3232

33-
| Token | Hex | Role |
34-
|-------|-----|------|
35-
| Green (ink, on light) | `#356852` | snakes/text on cream |
36-
| Green (surface) | `#2f5e4a` | green-background fills |
37-
| Gold (on light) | `#c98a00` | accent on cream |
38-
| Gold (on dark) | `#f0b528` | accent on green |
39-
| Cream | `#f4f1e8` | light surface; **and** the light "ink" on green (not pure white) |
40-
41-
Two-color pairing: the **top-left** snake + `MODERN` use the green/cream
42-
"structure" color; the **bottom-right** snake + `PYTHON` use gold. On cream the
43-
structure color is green `#356852`; on green it is cream `#f4f1e8`.
33+
Five tokens (green ink / green surface / gold-light / gold-dark / cream), now
34+
authoritative in `brand/build/tokens.py`. The two-color pairing is the load-bearing
35+
idea: the **top-left** snake + `MODERN` carry the "structure" color, the
36+
**bottom-right** snake + `PYTHON` carry gold — and "structure" resolves to green on
37+
cream, cream on green. That `struct`/`gold` role split is what every geometry
38+
function takes as parameters.
4439

4540
### Typeface
4641

47-
**Jost** (SIL OFL — free for commercial use, embedding, and outlining), weight
48-
400. Wordmark glyphs are **outlined to SVG paths** at build time so assets
49-
render without the font installed (same approach the repo already uses for
50-
JetBrains Mono). Jost + its OFL license are vendored under `brand/build/fonts/`.
42+
**Jost** (SIL OFL — free for commercial use, embedding, and outlining), weight 400,
43+
vendored with its license under `brand/build/fonts/`. Wordmark glyphs are **outlined
44+
to SVG paths** at build time so assets render without the font installed. See
45+
`architecture/site-branding.md`.
5146

5247
---
5348

54-
## Geometry (authoritative coordinates)
55-
56-
### Icon (100×100 viewBox) — favicon / apple-touch / avatar
57-
58-
One mark (`_icon_mark`), used full-bleed by `icon()` and padded by `icon_circle()`.
59-
Snakes reach the borders; each has a square **block head** at one end and a
60-
**diagonal-cut tail** (outer edge straight, inner edge sliced) at the other. Tail
61-
polygon bases overlap the stroke by ~2px so tail and body render as one shape.
62-
```
63-
bg: rect 0 0 100 100 fill BG (square, no rx)
64-
snake TL: path "M15 68 L15 15 L68 15" stroke STRUCT width 11 (butt, miter)
65-
head TL: rect x61 y8 w14 h14 rx2 fill STRUCT
66-
tail TL: polygon "9.5,66 20.5,66 20.5,68 9.5,79" fill STRUCT
67-
snake BR: path "M85 32 L85 85 L32 85" stroke GOLD width 11
68-
head BR: rect x25 y78 w14 h14 rx2 fill GOLD
69-
tail BR: polygon "90.5,34 79.5,34 79.5,32 90.5,21" fill GOLD
70-
chevron: polyline "45,40 57,50 45,60" stroke GOLD width 6 (round)
71-
```
72-
`icon_circle` wraps the mark in `translate(50,50) scale(0.74) translate(-50,-50)`
73-
so it fits inside the inscribed circle with margin.
74-
On green: BG `#2f5e4a`, STRUCT (top-left snake) cream `#f4f1e8`, GOLD (bottom-right
75-
snake + chevron) `#f0b528`.
76-
77-
### Wordmark lockup (540×250 viewBox — the unit scaled into cards)
78-
79-
```
80-
snake TL: path "M138 122 L138 50 L210 50" stroke STRUCT width 8 (butt, miter)
81-
snake BR: path "M402 128 L402 200 L330 200" stroke GOLD width 8
82-
head TL: rect x202.5 y42.5 w15 h15 rx3 fill STRUCT
83-
head BR: rect x322.5 y192.5 w15 h15 rx3 fill GOLD
84-
tail TL: polygon "134,120 142,120 142,122 134,130" fill STRUCT
85-
tail BR: polygon "406,130 398,130 398,128 406,120" fill GOLD
86-
MODERN: x270 y126 Jost 400 size 50 fill STRUCT text-anchor middle textLength 210
87-
PYTHON: x270 y166 Jost 400 size 50 fill GOLD text-anchor middle textLength 210
88-
```
89-
`textLength 210` + `lengthAdjust="spacingAndGlyphs"` pins each line's width so
90-
the crop marks frame the text exactly regardless of renderer. Baselines 126/166
91-
= **gap 40**.
49+
## Geometry
50+
51+
The coordinates are generated by `brand/build/geometry.py``_icon_mark` (the
52+
100×100 icon), `icon()` / `icon_circle()` (full-bleed and padded-for-circular-crop
53+
variants), and `lockup_body()` (the 540×250 wordmark unit scaled into the cards) —
54+
and are pinned by `tests/test_geometry.py`. The code is the authority; the two
55+
constraints worth knowing *why* about:
56+
57+
- **Tail polygons overlap the stroke by ~2px** so each snake's tail and body
58+
render as one continuous shape rather than showing a seam.
59+
- **`textLength` + `lengthAdjust="spacingAndGlyphs"`** pins each wordmark line's
60+
width, so the crop marks frame the text identically regardless of renderer.
9261

9362
---
9463

planning/changes/2026-06-30.03-png-optimization.md

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,24 +48,12 @@ value is a smaller repo and smaller cards copied into the 7 docs repos
4848
### 1. Quantization in `raster.py`
4949

5050
`export_png` currently shells out to `rsvg-convert` and returns `bool`. Add a
51-
post-write step that re-saves the PNG palette-quantized:
52-
53-
```python
54-
_PNG_COLORS = 32 # palette size; flat art needs few. 8 already looked clean; 32 = headroom.
55-
56-
57-
def _quantize_png(path: Path, colors: int = _PNG_COLORS) -> None:
58-
"""Re-save a PNG as an indexed-palette image (visually lossless for flat art).
59-
FASTOCTREE preserves alpha, so it is correct for both the opaque social cards
60-
and the transparent marks. No-op (leaves the RGBA file) if Pillow is absent."""
61-
try:
62-
from PIL import Image
63-
except ModuleNotFoundError:
64-
return
65-
im = Image.open(path).convert("RGBA")
66-
q = im.quantize(colors=colors, method=Image.Quantize.FASTOCTREE)
67-
q.save(path, format="PNG", optimize=True)
68-
```
51+
post-write step that re-saves the PNG palette-quantized — shipped as
52+
`raster.py::_quantize_png` (Pillow `FASTOCTREE`, `_PNG_COLORS` palette), which is
53+
the authority. Two choices worth the *why*: **FASTOCTREE** because it preserves
54+
alpha, so the one function is correct for both the opaque social cards and the
55+
transparent marks; and the quantize step **no-ops if Pillow is absent** rather
56+
than failing the build.
6957

7058
`export_png` calls `_quantize_png(png_path)` after a successful `rsvg-convert`.
7159
If `rsvg-convert` is absent there's no PNG and nothing to quantize (unchanged

planning/changes/2026-07-02.02-aiohttp-brand.md

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,23 +49,8 @@ substitute `{color}` for the two `{GOLD}` occurrences. This is backward
4949
compatible — the only existing caller (`db_retry`) omits the argument and keeps
5050
`GOLD`.
5151

52-
```python
53-
def _circ_arc(cx: float, cy: float, rad: float, a0: float, a1: float, w: float,
54-
color: str = GOLD) -> str:
55-
... # stroke and arrowhead now use {color} instead of {GOLD}
56-
57-
58-
def async_loop(cx: float, cy: float, r: float) -> str:
59-
"""aiohttp cue: an async event-loop cycle (two chasing arrows) knocked out
60-
of a gold disc."""
61-
rad = r * 0.52
62-
w = 3.4
63-
loop = (
64-
_circ_arc(cx, cy, rad, 25, 165, w, color=CREAM)
65-
+ _circ_arc(cx, cy, rad, 205, 345, w, color=CREAM)
66-
)
67-
return f'<circle cx="{cx}" cy="{cy}" r="{r:.1f}" fill="{GOLD}"/>' + loop
68-
```
52+
Shipped as `symbols.py::async_loop` (with the widened `_circ_arc`); those are the
53+
authority.
6954

7055
`CREAM` is already imported in `symbols.py`. The mark uses only `GOLD` and
7156
`CREAM`, both in the allowed palette, so `test_only_allowed_colours` passes.

0 commit comments

Comments
 (0)