Merge remote-tracking branch 'origin/main' into codex/pr32-final
# Conflicts: # .github/workflows/ci.yml
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
name: Bug report
|
||||
description: A diagram, script, or import path misbehaves — report it so we can fix it.
|
||||
title: "[Bug]: "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for reporting. Before submitting, check that this isn't an already-open issue.
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Area
|
||||
description: What part of the project is affected?
|
||||
options:
|
||||
- A diagram example
|
||||
- Style guide / skin / linter
|
||||
- draw.io import
|
||||
- Mermaid import
|
||||
- Export (SVG / PNG)
|
||||
- Documentation
|
||||
- CI / tooling
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Describe the unexpected behavior. Include what you expected instead.
|
||||
placeholder: "The linter accepts a diagram that breaks the accessible SVG contract…"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: repro
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Concrete steps — commands, file names, and inputs matter here.
|
||||
placeholder: |
|
||||
1. Run `python3 scripts/lint-skin.py <file>`
|
||||
2. …
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment
|
||||
description: OS, Python version, agent (Claude Code / Codex / Pi) if relevant
|
||||
placeholder: "macOS 14, Python 3.11, Claude Code"
|
||||
- type: checkboxes
|
||||
id: gates
|
||||
attributes:
|
||||
label: Validation gates
|
||||
description: If relevant, which gates have you already run?
|
||||
options:
|
||||
- label: "`python3 scripts/test-lint-a11y.py`"
|
||||
- label: "`python3 scripts/lint-skin.py --all --baseline`"
|
||||
- label: "`python3 scripts/verify-drawio-import.py`"
|
||||
- label: "`python3 scripts/verify-mermaid-import.py`"
|
||||
- label: "I haven't run any gates"
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant output
|
||||
description: Paste error messages or linter findings, trimmed to what matters.
|
||||
render: text
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Security vulnerability
|
||||
url: https://github.com/cathrynlavery/diagram-design/security/advisories/new
|
||||
about: Report a suspected vulnerability privately — do not open a public issue for it.
|
||||
@@ -0,0 +1,45 @@
|
||||
name: Feature request
|
||||
description: Suggest a new diagram type, primitive, or improvement.
|
||||
title: "[Feature]: "
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Ideas are welcome. Before submitting, check the README's "When *not* to use this skill" section — sometimes the right answer is a table, not a diagram.
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: What are you trying to show?
|
||||
description: Describe the situation and what the reader needs to understand. A sketch or ASCII mock helps a lot.
|
||||
placeholder: "I want to show how... and the reader should see..."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: proposal
|
||||
attributes:
|
||||
label: Proposed solution
|
||||
description: How could the skill serve this? A new type, a primitive, a change to an existing one, docs?
|
||||
placeholder: "A new `type-*.md` for …, or an extension of …"
|
||||
- type: dropdown
|
||||
id: scope
|
||||
attributes:
|
||||
label: Scope
|
||||
description: Roughly how much surface does this touch?
|
||||
options:
|
||||
- New diagram type (reference + 3 examples + gallery)
|
||||
- New or changed primitive
|
||||
- Import grammar support
|
||||
- Tooling / scripts / CI
|
||||
- Documentation only
|
||||
- Not sure yet — help me scope it
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: philosophy
|
||||
attributes:
|
||||
label: Design-system fit
|
||||
description: Features in this project must fit the editorial philosophy in SKILL.md §1–§2.
|
||||
options:
|
||||
- label: "I've checked the anti-patterns and 'When not to use' — this still seems worth drawing"
|
||||
- label: "I'm willing to contribute the implementation or pair with a maintainer"
|
||||
@@ -0,0 +1,26 @@
|
||||
## What does this PR do?
|
||||
|
||||
<!-- One or two sentences: what changed and why. -->
|
||||
|
||||
## Validation gates
|
||||
|
||||
<!-- Mark what you ran and that it passed. CI runs all of these on the PR too. -->
|
||||
|
||||
- [ ] `python3 scripts/test-lint-a11y.py`
|
||||
- [ ] `python3 scripts/lint-skin.py --all --baseline`
|
||||
- [ ] `python3 scripts/verify-sequence-oauth.py`
|
||||
- [ ] `python3 scripts/verify-drawio-import.py`
|
||||
- [ ] `python3 scripts/verify-mermaid-import.py`
|
||||
- [ ] `git diff --exit-code` green after `python3 scripts/build-icons.py` (if icons changed)
|
||||
- [ ] Docs updated in the same PR where behavior changed
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] No new entries added to `scripts/lint-skin-baseline.txt`
|
||||
- [ ] Generated and source files are consistent (extractor ↔ verifier ↔ reference ↔ command)
|
||||
- [ ] Accessible SVG contract satisfied for new/changed examples
|
||||
- [ ] Code of Conduct respected
|
||||
|
||||
## Related issues
|
||||
|
||||
<!-- Link any issues this closes, e.g. "Closes #12". -->
|
||||
@@ -32,6 +32,12 @@ jobs:
|
||||
- name: Verify URL SVG normalization
|
||||
run: python3 scripts/test-build-icons-quoted-attributes.py
|
||||
|
||||
- name: Verify Devicon normalization
|
||||
run: python3 scripts/test-build-icons-devicon.py
|
||||
|
||||
- name: Verify mojibake repair utility
|
||||
run: python3 scripts/test-fix-mojibake.py
|
||||
|
||||
- name: Run skin linter
|
||||
run: python3 scripts/lint-skin.py --all --baseline
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Deploy gallery to GitHub Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Configure Pages
|
||||
uses: actions/configure-pages@v6
|
||||
|
||||
- name: Upload gallery artifact
|
||||
uses: actions/upload-pages-artifact@v5
|
||||
with:
|
||||
path: skills/diagram-design/assets
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v5
|
||||
@@ -0,0 +1,134 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official email address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
[github.com/cathrynlavery/diagram-design/issues](https://github.com/cathrynlavery/diagram-design/issues)
|
||||
or via private vulnerability reporting ([SECURITY.md](SECURITY.md) links the
|
||||
private path). All complaints will be reviewed and investigated promptly and
|
||||
fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
# Contributing to Diagram Design
|
||||
|
||||
Thanks for wanting to contribute — this project only gets better with more eyes on it.
|
||||
|
||||
Please read [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) first. All contributions are expected to keep the community welcoming.
|
||||
|
||||
---
|
||||
|
||||
## What this project is
|
||||
|
||||
Diagram Design is an agent skill (Claude Code, Codex, Pi) that produces editorial-quality diagrams as self-contained HTML files. The repo is documentation-first: `skills/diagram-design/SKILL.md` is the index, each of the 27 diagram types has its own reference file, and the extractor scripts in `skills/diagram-design/scripts/` turn draw.io and Mermaid sources into a structured IR.
|
||||
|
||||
See [README.md](README.md) for the full picture, including the design system and the import/export flows.
|
||||
|
||||
---
|
||||
|
||||
## Before you start
|
||||
|
||||
- **Create an issue first** for anything non-trivial (new type, behavior change, import grammar work). Small fixes and docs can go straight to a PR.
|
||||
- **Work on a branch** — never commit directly to `main`.
|
||||
- **Keep the scope tight.** One PR = one concern. Mixing a new diagram type with a docs rewrite makes review slow.
|
||||
- **Python 3.10+ is required** for the development scripts (CI runs 3.11).
|
||||
|
||||
---
|
||||
|
||||
## Validation gates
|
||||
|
||||
Every validation gate below must pass before a PR is ready. They also run automatically as GitHub Actions CI (`.github/workflows/ci.yml`).
|
||||
|
||||
| What it checks | Command |
|
||||
|---|---|
|
||||
| Accessible SVG contract (unit tests for the a11y linter) | `python3 scripts/test-lint-a11y.py` |
|
||||
| Skin conformance of every example (colors, fonts, a11y) | `python3 scripts/lint-skin.py --all --baseline` |
|
||||
| A single file, e.g. a new example | `python3 scripts/lint-skin.py skills/diagram-design/assets/example-my-type.html` |
|
||||
| Sequence-doc consistency (ATL fragments, budgets) | `python3 scripts/verify-sequence-oauth.py` |
|
||||
| draw.io import path (real extractor vs fixtures + docs sync) | `python3 scripts/verify-drawio-import.py` |
|
||||
| Mermaid import path (grammars, adversarial input, caps, docs sync) | `python3 scripts/verify-mermaid-import.py` |
|
||||
| Generated icon assets are up to date (`icons.html`, `primitive-icons.md`) | `python3 scripts/build-icons.py` then `git diff --exit-code` on the two generated files |
|
||||
|
||||
Run them all at once before pushing:
|
||||
|
||||
```bash
|
||||
python3 scripts/test-lint-a11y.py \
|
||||
&& python3 scripts/lint-skin.py --all --baseline \
|
||||
&& python3 scripts/verify-sequence-oauth.py \
|
||||
&& python3 scripts/verify-drawio-import.py \
|
||||
&& python3 scripts/verify-mermaid-import.py
|
||||
```
|
||||
|
||||
### If a gate fails
|
||||
|
||||
- **`lint-skin.py`:** the failure message names the file, line, and category (`color`, `font-family`, `a11y`, `external-asset`, `pure-black`, `script`). Colors must come from the palette in `skills/diagram-design/references/style-guide.md`; fonts from the allowed list; diagrams must satisfy the accessible SVG contract (see below).
|
||||
- **`verify-*.py`:** the extractor's real behavior no longer matches its fixture or the documentation, or the reference/command/prompt wiring drifted. Fix the source of truth — do not widen a test to avoid a failure.
|
||||
- **Icon assets:** you changed `scripts/vendor/icons/` or `scripts/build-icons.py` and the generated files went stale. Rerun `python3 scripts/build-icons.py` and commit the regenerated files.
|
||||
|
||||
Do **not** add a file to `scripts/lint-skin-baseline.txt` to get your example through. The baseline exists only for legacy pre-2.0 examples that legitimately predate the current skin, and it still receives a11y checks.
|
||||
|
||||
---
|
||||
|
||||
## The accessible SVG contract (a11y)
|
||||
|
||||
Every diagram `<svg>` must satisfy the contract enforced by the linter:
|
||||
|
||||
1. `role="img"` and `aria-labelledby` naming the `<title>` **and** `<desc>`.
|
||||
2. `<title>` is the **first child** of `<svg>` (before `<defs>`).
|
||||
3. IDs are prefixed per diagram and variant: `<slug>-title` / `<slug>-desc` — for `example-loop-dark.html` the slug is `loop-dark`, so the IDs are `loop-dark-title` / `loop-dark-desc`. Bare `title`/`desc` IDs and duplicate IDs are rejected.
|
||||
4. `<title>` is the short subject name (≈ the `<h1>`, ≤ 60 chars); `<desc>` is one sentence describing the *content*, not the geometry.
|
||||
5. Purely decorative SVGs (`aria-hidden="true"`) are exempt.
|
||||
|
||||
The contract lives in `scripts/lint-skin.py` (`lint_accessible_svgs`) and is unit-tested by `scripts/test-lint-a11y.py`. When in doubt, pattern-match an existing example.
|
||||
|
||||
---
|
||||
|
||||
## Working on examples (diagrams)
|
||||
|
||||
Every diagram type ships three variants: minimal light (`example-<type>.html`), minimal dark (`example-<type>-dark.html`), and full editorial (`example-<type>-full.html`).
|
||||
|
||||
1. Copy the closest template (`skills/diagram-design/assets/template.html`, `template-dark.html`, or `template-full.html`).
|
||||
2. Load the matching `references/type-<name>.md` and follow its layout conventions.
|
||||
3. Replace the eyebrow, h1, and SVG body; replace the `[diagram-slug]` placeholders with your file's slug and keep the `<title>`/`<desc>` slots filled.
|
||||
4. Run the taste gate in `SKILL.md` §9, then the linter:
|
||||
|
||||
```bash
|
||||
python3 scripts/lint-skin.py skills/diagram-design/assets/example-my-type.html
|
||||
```
|
||||
|
||||
New examples should be added to the gallery (`assets/index.html`) so they stay browsable.
|
||||
|
||||
## Adding a new diagram type
|
||||
|
||||
1. Write `skills/diagram-design/references/type-<name>.md` — layout conventions, anti-patterns, and a worked pattern for that type. Mirror an existing reference's structure.
|
||||
2. Add the row to the selection table in `skills/diagram-design/SKILL.md` §3.
|
||||
3. Add the three example variants (see above) and register them in the gallery.
|
||||
4. Run the full gate suite — new examples are linted automatically by `--all`.
|
||||
|
||||
## Changing the icon set
|
||||
|
||||
Icons are generated, never hand-edited:
|
||||
|
||||
1. Add or replace the source SVG in `scripts/vendor/icons/<source>/` (`tabler/`, `simple/`, …). Keep license provenance in `THIRD_PARTY_LICENSES.md` accurate.
|
||||
2. Regenerate and verify:
|
||||
|
||||
```bash
|
||||
python3 scripts/build-icons.py
|
||||
git diff --exit-code -- skills/diagram-design/assets/icons.html skills/diagram-design/references/primitive-icons.md
|
||||
```
|
||||
|
||||
## Touching the import paths
|
||||
|
||||
- draw.io: `skills/diagram-design/scripts/drawio_extract.py` — must pass `scripts/verify-drawio-import.py`, which drives the extractor against `scripts/fixtures/sample-architecture.drawio` in all four container formats (raw XML, deflate+base64, PNG-embedded, SVG-embedded).
|
||||
- Mermaid: `skills/diagram-design/scripts/mermaid_extract.py` — must pass `scripts/verify-mermaid-import.py`, which covers every supported grammar, multi-block Markdown, adversarial labels, trust-boundary behavior, resource caps, and named failures.
|
||||
|
||||
Both scripts treat their input as **untrusted data** — they never render, fetch, or execute source content. Keep it that way. If you add a grammar or a new security boundary, extend the corresponding verifier with a fixture before merging.
|
||||
|
||||
Documentation and wiring must stay in sync: the import references, `SKILL.md` §11, the slash commands in `commands/`, and the Pi prompt templates in `prompts/` each describe the same flows. The verifiers check this — keep both sides updated in one PR.
|
||||
|
||||
## Documentation
|
||||
|
||||
Most of this repo *is* documentation. When behavior changes, update the affected reference files and the README in the same PR. Loose ends here are what the verifiers and reviewers will catch.
|
||||
|
||||
---
|
||||
|
||||
## Commit and PR conventions
|
||||
|
||||
Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/): `type(scope): summary`, e.g. `fix(import): support current Mermaid syntax`, `ci: verify Mermaid imports`, `docs(onboarding): clarify the URL flow`. Keep summaries short and imperative.
|
||||
|
||||
A good PR:
|
||||
|
||||
- has a clear title and a description that says *what* changed and *why*;
|
||||
- mentions how you tested it (which gates you ran);
|
||||
- keeps generated and source files consistent (extractor + verifier + reference + command in one change);
|
||||
- is rebased on `main` and green on CI.
|
||||
|
||||
## Questions?
|
||||
|
||||
Open a discussion or comment on the relevant issue. If it's about security, use the private reporting path in [SECURITY.md](SECURITY.md).
|
||||
@@ -81,7 +81,7 @@ All 27 diagrams ship in three variants: minimal light, minimal dark, and full-ed
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
**Browse the live gallery:** open [`skills/diagram-design/assets/index.html`](skills/diagram-design/assets/index.html) in your browser to flip through all 27 diagrams with light / dark / full-editorial tabs.
|
||||
**Browse the live gallery:** [cathrynlavery.github.io/diagram-design](https://cathrynlavery.github.io/diagram-design/) — or open [`skills/diagram-design/assets/index.html`](skills/diagram-design/assets/index.html) locally to flip through all 27 diagrams with light / dark / full-editorial tabs.
|
||||
|
||||
---
|
||||
|
||||
@@ -408,6 +408,12 @@ Before drawing, ask: *would a reader learn more from this than from a well-writt
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome — new diagram types, import grammar support, examples, docs, and tooling. See [CONTRIBUTING.md](CONTRIBUTING.md) for the validation gates and workflows, and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for community standards.
|
||||
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
Made by **Cathryn Lavery** — founder of [BestSelf.co](https://bestself.co?utm_source=diagram-design&utm_medium=readme&utm_campaign=github&utm_content=bio). I write about AI, entrepreneurship, and designing nice-looking things at [littlemight.com](https://littlemight.com?utm_source=diagram-design&utm_medium=readme&utm_campaign=github&utm_content=bio) — blog + newsletter.
|
||||
|
||||
@@ -314,6 +314,10 @@ def normalize_url(raw: str) -> str:
|
||||
def normalize_devicon(raw: str) -> str:
|
||||
"""Devicon SVGs have colored fills and a 128x128 or 32x32 viewBox.
|
||||
Strip fills/classes and rewrite to 24x24 currentColor."""
|
||||
viewbox_match = re.search(
|
||||
r'\bviewBox\s*=\s*(["\'])(.*?)\1', raw, flags=re.IGNORECASE
|
||||
)
|
||||
viewbox = viewbox_match.group(2).strip() if viewbox_match else "0 0 128 128"
|
||||
inner = re.search(r"<svg\b[^>]*>(.*?)</svg>", raw, flags=re.DOTALL | re.IGNORECASE)
|
||||
if not inner:
|
||||
raise ValueError("no <svg> in Devicon payload")
|
||||
@@ -325,7 +329,7 @@ def normalize_devicon(raw: str) -> str:
|
||||
body = re.sub(r'\bclass="[^"]*"', 'fill="currentColor"', body)
|
||||
body = re.sub(r"\s+", " ", body).strip()
|
||||
return (
|
||||
'<svg aria-hidden="true" width="24" height="24" viewBox="0 0 128 128" '
|
||||
f'<svg aria-hidden="true" width="24" height="24" viewBox="{viewbox}" '
|
||||
f'fill="currentColor">{body}</svg>'
|
||||
)
|
||||
|
||||
|
||||
@@ -17,8 +17,10 @@ PAIRS = [
|
||||
('…', '…'), # horizontal ellipsis
|
||||
('•', '•'), # bullet
|
||||
('→', '→'), # right arrow
|
||||
('↑', '←'), # left arrow
|
||||
('↑', '↑'), # up arrow (same prefix collision - rare)
|
||||
# CP1252 leaves byte 0x90 undefined, so spell the left-arrow corruption
|
||||
# with a Unicode escape instead of embedding an invisible control character.
|
||||
('\u00e2\u2020\u0090', '←'), # left arrow
|
||||
('↑', '↑'), # up arrow
|
||||
('↓', '↓'), # down arrow
|
||||
('≤', '≤'), # less-than-or-equal
|
||||
('≥', '≥'), # greater-than-or-equal
|
||||
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Regression tests for Devicon viewBox preservation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
BUILD = ROOT / "scripts" / "build-icons.py"
|
||||
|
||||
|
||||
def load_build_module():
|
||||
sys.dont_write_bytecode = True
|
||||
spec = importlib.util.spec_from_file_location("diagram_design_build_icons", BUILD)
|
||||
if spec is None or spec.loader is None:
|
||||
raise AssertionError("could not load build-icons.py")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def main() -> int:
|
||||
build_icons = load_build_module()
|
||||
svg = '<svg viewBox="0 0 32 32"><path fill="#123456" d="M0 0h32v32H0z"/></svg>'
|
||||
normalized = build_icons.normalize_devicon(svg)
|
||||
if 'viewBox="0 0 32 32"' not in normalized:
|
||||
raise AssertionError(f"source viewBox was not preserved: {normalized}")
|
||||
if 'fill="#123456"' in normalized:
|
||||
raise AssertionError("source fill escaped currentColor normalization")
|
||||
if 'fill="currentColor"' not in normalized:
|
||||
raise AssertionError("normalized icon does not inherit currentColor")
|
||||
print("PASS: Devicon normalization preserves non-128 source viewBoxes")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Regression tests for the mojibake repair utility."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
SCRIPT = ROOT / "scripts" / "fix-mojibake.py"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
# These are the strings produced when UTF-8 arrow bytes are decoded as
|
||||
# Windows-1252. Byte 0x90 is undefined in CP1252, hence the explicit escape.
|
||||
source = "right →, left \u00e2\u2020\u0090, up ↑, down ↓\n"
|
||||
expected = "right →, left ←, up ↑, down ↓\n"
|
||||
with tempfile.TemporaryDirectory(prefix="diagram-design-mojibake-") as directory:
|
||||
path = Path(directory) / "fixture.md"
|
||||
path.write_text(source, encoding="utf-8")
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(SCRIPT), str(path)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise AssertionError(f"repair failed: {result.stdout}{result.stderr}")
|
||||
actual = path.read_text(encoding="utf-8")
|
||||
if actual != expected:
|
||||
raise AssertionError(f"expected {expected!r}, got {actual!r}")
|
||||
print("PASS: mojibake arrow sequences repair to the intended directions")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user