Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions architecture/community.md
Original file line number Diff line number Diff line change
@@ -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.
12 changes: 12 additions & 0 deletions architecture/org-profile.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<pkg>/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.
Expand Down
16 changes: 8 additions & 8 deletions planning/changes/2026-06-24.01-promotion-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)*

Expand Down Expand Up @@ -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.
16 changes: 10 additions & 6 deletions planning/changes/2026-06-27.01-community-channels.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down Expand Up @@ -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.
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).
77 changes: 23 additions & 54 deletions planning/changes/2026-06-28.01-org-favicon-social-card.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

---

Expand Down
24 changes: 6 additions & 18 deletions planning/changes/2026-06-30.03-png-optimization.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 2 additions & 17 deletions planning/changes/2026-07-02.02-aiohttp-brand.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'<circle cx="{cx}" cy="{cy}" r="{r:.1f}" fill="{GOLD}"/>' + 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.
Expand Down
Loading