Repository navigation
feat(design): DL-1 unified design-language token layer (pmoves/design/) #1827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 10 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
a337a5e
docs(design): DL-0 unified design-language spec — themable engine
hunnibear 2de3c85
docs(design): DL-1 plan + claim register entry (5090 lane, credits 40…
hunnibear 5e87922
feat(design): DL-1 base + theme token definitions
hunnibear cbc3dd6
feat(design): DL-1 token generator — registry accent resolution (TDD)
hunnibear e46a466
feat(design): DL-1 emit tokens.css + tokens.ts; generate armor + skin
hunnibear 5d1bf2e
feat(design): DL-1 ThemeProvider + CSP-clean token preview page
hunnibear b815ff3
docs(design): DL-1 README + make design-tokens target (credits W1 lane)
hunnibear 530d2e4
chore(design): DL-1 test lint cleanup + follow-on lanes handoff
hunnibear d7fd81e
docs(agnote): DL-1 UPDATE — built + verified, PR #1827 open (awaiting…
hunnibear 12e179d
fix(design): DL-1 review follow-ups — value guard, clear errors, drif…
hunnibear f570516
fix(design): address DL-1 review threads (Orbitron quote, test rigor,…
POWERFULMOVES 0c8a750
Merge branch 'main' into feat/dl-1-unified-design-tokens
POWERFULMOVES File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
414 changes: 414 additions & 0 deletions
414
docs/superpowers/plans/2026-06-16-dl-1-design-token-layer.md
Large diffs are not rendered by default.
Oops, something went wrong.
199 changes: 199 additions & 0 deletions
199
docs/superpowers/specs/2026-06-15-pmoves-unified-design-language.md
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # DL-1: the generated token build is intentionally committed so consumers | ||
| # (A2UI, Tailwind, preview.html) need no build step. Override the repo-root | ||
| # `build/` ignore for this directory only. | ||
| !build/ | ||
| !build/** |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| # PMOVES Design Token Layer (DL-1) | ||
|
|
||
| A build-time **design token layer** for PMOVES.AI. It reads the canonical persona | ||
| registry, merges it with structural base tokens and per-theme overlays, and emits | ||
| CSS custom properties + a typed TS token map plus a CSP-clean preview page. | ||
|
|
||
| DL-1 is the **token layer** (Model A in the A/B/C plan). It is intentionally | ||
| framework-free: stdlib Python + PyYAML for the generator, vanilla JS/CSS/HTML for | ||
| the runtime and preview. | ||
|
|
||
| ## The registry contract (source of truth) | ||
|
|
||
| The accent colors come from **`pmoves/config/agent_signatures.yaml`** — the canonical | ||
| agent/persona signature registry. DL-1 **reads it read-only**; it never modifies the | ||
| registry or its schema. | ||
|
|
||
| - Top-level key: `signatures` → `{ <agent-id>: { color, accent, ... } }`. | ||
| - A theme references an agent **by id** (e.g. `claude-opus`, `4090-claude`, `darkxside`). | ||
| The generator resolves that id to the agent's `color` (primary/secondary) and | ||
| `accent` (the soft variant). | ||
| - If the registry's hex values change, the emitted tokens change on the next | ||
| `make design-tokens` run — values are sourced at build time, never hand-copied. | ||
| (The test fixtures in `tests/test_generate.py` assert the current registry values; | ||
| update them if the registry changes.) | ||
|
|
||
| ## How to regenerate | ||
|
|
||
| ```bash | ||
| make -C pmoves design-tokens | ||
| # or, from this directory: | ||
| uv run --no-project --with pyyaml python generate.py | ||
| ``` | ||
|
|
||
| This writes `build/tokens.<theme>.css` and `build/tokens.ts`. The `build/` output is | ||
| **committed** so consumers (Notebook UI, A2UI, Tailwind, the CF site) need no build | ||
| step. A scoped `.gitignore` here re-includes `build/` (the repo root ignores `build/`). | ||
|
|
||
| ## Themes | ||
|
|
||
| | Theme | File | Lead accent | Notes | | ||
| |-------|------|-------------|-------| | ||
| | `pmoves-armor` (default) | `themes/pmoves-armor.json` | `claude-opus` violet | Cool armor; also matches bare `:root`. | | ||
| | `darkxside-skin` | `themes/darkxside-skin.json` | `darkxside` crimson | Warm persona skin. | | ||
|
|
||
| ## Token naming | ||
|
|
||
| All custom properties are namespaced `--pm-*`: | ||
|
|
||
| - Accents: `--pm-accent`, `--pm-accent-soft`, `--pm-accent-2`, `--pm-signature` | ||
| - Surfaces: `--pm-bg`, `--pm-bg-tint`, `--pm-surface`, `--pm-void`, `--pm-void-elevated` | ||
| - Borders: `--pm-border-subtle`, `--pm-border-strong` | ||
| - Ink: `--pm-ink`, `--pm-ink-dim`, `--pm-ink-mute`, `--pm-ink-inverse` | ||
| - Structure: `--pm-radius`, `--pm-space-{xs,sm,md,lg,xl}` | ||
| - Type: `--pm-font-display`, `--pm-font-body`, `--pm-font-mono` | ||
|
|
||
| ## Runtime + preview | ||
|
|
||
| - `theme-provider.js` — dependency-free `setTheme` / `currentTheme` / `toggleTheme`. | ||
| DL-3 will extend it with `setPersona()` resolving via the BoTZ Gateway. | ||
| - `preview.html` + `preview.css` + `preview.js` — self-contained, **CSP-clean** | ||
| showcase (no inline style/script bodies, no CDN). Serve and open: | ||
| `uv run python -m http.server 8799` → `http://localhost:8799/preview.html`. | ||
|
|
||
| ## A/B/C model | ||
|
|
||
| - **A (shipped here):** token layer + generator + ThemeProvider + proof surface. | ||
| - **B (DL-2):** roll tokens into Notebook (`pmoves/ui`) + A2UI `ProvenancePalette` + CF site. | ||
| - **C (DL-3):** persona-adaptive runtime (`theme-provider.setPersona` → gateway). | ||
|
|
||
| ## Credits — W1 theme lane (4090-claude) | ||
|
|
||
| The persona signature registry (`agent_signatures.yaml`) is owned and maintained by | ||
| the **W1 theme lane (4090-claude, PRs #1065/#1101)**. DL-1 consumes it with | ||
| **additive reads only** and does not touch any W1-owned file | ||
| (`pmoves/tools/agent_terminal_theme.py`, `pmoves/tools/botz_cli.py`, | ||
| `pmoves/services/botz-gateway/main.py`, or the registry itself). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| /* generated by pmoves/design/generate.py — do not edit */ | ||
| :root[data-theme="darkxside-skin"] { | ||
| --pm-accent: #E11D48; | ||
| --pm-accent-2: #7C3AED; | ||
| --pm-accent-soft: #FB7185; | ||
| --pm-bg: #0a0608; | ||
| --pm-bg-tint: #140a0e; | ||
| --pm-border-strong: rgba(255,255,255,0.15); | ||
| --pm-border-subtle: rgba(255,255,255,0.08); | ||
| --pm-font-body: 'Exo 2', system-ui, sans-serif; | ||
| --pm-font-display: Orbitron, 'Exo 2', system-ui, sans-serif; | ||
|
POWERFULMOVES marked this conversation as resolved.
Resolved
|
||
| --pm-font-mono: 'JetBrains Mono', ui-monospace, monospace; | ||
| --pm-ink: #f8f8f8; | ||
| --pm-ink-dim: #a0a0a8; | ||
| --pm-ink-inverse: #050508; | ||
| --pm-ink-mute: #606068; | ||
| --pm-radius: 14px; | ||
| --pm-signature: #E11D48; | ||
| --pm-space-lg: 24px; | ||
| --pm-space-md: 16px; | ||
| --pm-space-sm: 8px; | ||
| --pm-space-xl: 40px; | ||
| --pm-space-xs: 4px; | ||
| --pm-surface: #12121a; | ||
| --pm-void: #050508; | ||
| --pm-void-elevated: #0a0a0f; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| /* generated by pmoves/design/generate.py — do not edit */ | ||
| :root[data-theme="pmoves-armor"], :root:not([data-theme]) { | ||
| --pm-accent: #7C3AED; | ||
| --pm-accent-2: #0D9488; | ||
| --pm-accent-soft: #A78BFA; | ||
| --pm-bg: #050508; | ||
| --pm-bg-tint: #0a0a12; | ||
| --pm-border-strong: rgba(255,255,255,0.15); | ||
| --pm-border-subtle: rgba(255,255,255,0.08); | ||
| --pm-font-body: 'Exo 2', system-ui, sans-serif; | ||
| --pm-font-display: Orbitron, 'Exo 2', system-ui, sans-serif; | ||
|
POWERFULMOVES marked this conversation as resolved.
Resolved
|
||
| --pm-font-mono: 'JetBrains Mono', ui-monospace, monospace; | ||
| --pm-ink: #f8f8f8; | ||
| --pm-ink-dim: #a0a0a8; | ||
| --pm-ink-inverse: #050508; | ||
| --pm-ink-mute: #606068; | ||
| --pm-radius: 14px; | ||
| --pm-signature: #E11D48; | ||
| --pm-space-lg: 24px; | ||
| --pm-space-md: 16px; | ||
| --pm-space-sm: 8px; | ||
| --pm-space-xl: 40px; | ||
| --pm-space-xs: 4px; | ||
| --pm-surface: #12121a; | ||
| --pm-void: #050508; | ||
| --pm-void-elevated: #0a0a0f; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| // generated by pmoves/design/generate.py — do not edit | ||
| export const themes = { | ||
| "darkxside-skin": { | ||
| "--pm-accent": "#E11D48", | ||
| "--pm-accent-2": "#7C3AED", | ||
| "--pm-accent-soft": "#FB7185", | ||
| "--pm-bg": "#0a0608", | ||
| "--pm-bg-tint": "#140a0e", | ||
| "--pm-border-strong": "rgba(255,255,255,0.15)", | ||
| "--pm-border-subtle": "rgba(255,255,255,0.08)", | ||
| "--pm-font-body": "'Exo 2', system-ui, sans-serif", | ||
| "--pm-font-display": "Orbitron, 'Exo 2', system-ui, sans-serif", | ||
| "--pm-font-mono": "'JetBrains Mono', ui-monospace, monospace", | ||
| "--pm-ink": "#f8f8f8", | ||
| "--pm-ink-dim": "#a0a0a8", | ||
| "--pm-ink-inverse": "#050508", | ||
| "--pm-ink-mute": "#606068", | ||
| "--pm-radius": "14px", | ||
| "--pm-signature": "#E11D48", | ||
| "--pm-space-lg": "24px", | ||
| "--pm-space-md": "16px", | ||
| "--pm-space-sm": "8px", | ||
| "--pm-space-xl": "40px", | ||
| "--pm-space-xs": "4px", | ||
| "--pm-surface": "#12121a", | ||
| "--pm-void": "#050508", | ||
| "--pm-void-elevated": "#0a0a0f" | ||
| }, | ||
| "pmoves-armor": { | ||
| "--pm-accent": "#7C3AED", | ||
| "--pm-accent-2": "#0D9488", | ||
| "--pm-accent-soft": "#A78BFA", | ||
| "--pm-bg": "#050508", | ||
| "--pm-bg-tint": "#0a0a12", | ||
| "--pm-border-strong": "rgba(255,255,255,0.15)", | ||
| "--pm-border-subtle": "rgba(255,255,255,0.08)", | ||
| "--pm-font-body": "'Exo 2', system-ui, sans-serif", | ||
| "--pm-font-display": "Orbitron, 'Exo 2', system-ui, sans-serif", | ||
| "--pm-font-mono": "'JetBrains Mono', ui-monospace, monospace", | ||
| "--pm-ink": "#f8f8f8", | ||
| "--pm-ink-dim": "#a0a0a8", | ||
| "--pm-ink-inverse": "#050508", | ||
| "--pm-ink-mute": "#606068", | ||
| "--pm-radius": "14px", | ||
| "--pm-signature": "#E11D48", | ||
| "--pm-space-lg": "24px", | ||
| "--pm-space-md": "16px", | ||
| "--pm-space-sm": "8px", | ||
| "--pm-space-xl": "40px", | ||
| "--pm-space-xs": "4px", | ||
| "--pm-surface": "#12121a", | ||
| "--pm-void": "#050508", | ||
| "--pm-void-elevated": "#0a0a0f" | ||
| } | ||
| } as const; | ||
| export type ThemeName = keyof typeof themes; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| # pmoves/design/generate.py | ||
| """DL-1 design token generator. Reads pmoves/config/agent_signatures.yaml | ||
| (registry owned by the W1 theme lane — 4090-claude PRs #1065/#1101; do NOT | ||
| modify the registry schema here) + base/theme JSON, emits CSS + TS tokens.""" | ||
| from __future__ import annotations | ||
| import json, pathlib, re | ||
| DESIGN = pathlib.Path(__file__).resolve().parent | ||
| REPO = DESIGN.parents[1] | ||
| REGISTRY = REPO / "pmoves" / "config" / "agent_signatures.yaml" | ||
|
|
||
| # Reject values that would break out of a CSS declaration or the generated TS | ||
| # string map. The registry is trusted, but a typo (stray ; } " or newline) would | ||
| # otherwise produce broken build/ output that still commits clean — a silent | ||
| # failure. Single quotes/commas/parens are allowed (font stacks, rgba()). | ||
| _UNSAFE = re.compile(r'[;{}"\n\r]') | ||
|
|
||
| def load_registry() -> dict: | ||
| import yaml | ||
| data = yaml.safe_load(REGISTRY.read_text(encoding="utf-8")) or {} | ||
| return data.get("signatures", {}) | ||
|
|
||
| def _agent(reg: dict, agent_id: str) -> dict: | ||
| if agent_id not in reg: | ||
| raise KeyError(f"theme references unknown agent id: {agent_id!r}") | ||
| return reg[agent_id] | ||
|
|
||
| def _field(agent: dict, field: str, agent_id: str) -> str: | ||
| if field not in agent: | ||
| raise KeyError(f"agent {agent_id!r} missing required theme field {field!r}") | ||
| return agent[field] | ||
|
|
||
| def _safe(key: str, val: str) -> str: | ||
| if not isinstance(val, str) or _UNSAFE.search(val): | ||
| raise ValueError(f"unsafe/invalid token value for {key!r}: {val!r}") | ||
| return val | ||
|
|
||
| def resolve_theme(theme: dict, reg: dict) -> dict: | ||
| base = json.loads((DESIGN / "tokens.base.json").read_text(encoding="utf-8")) | ||
| v: dict[str, str] = {} | ||
| # structural base | ||
| for k, val in base["color"].items(): | ||
| v[f"--pm-{k}"] = val | ||
| v["--pm-radius"] = base["radius"]["lg"] | ||
| for k, val in base["space"].items(): | ||
| v[f"--pm-space-{k}"] = val | ||
| v["--pm-font-display"] = base["font"]["display"] | ||
| v["--pm-font-body"] = base["font"]["body"] | ||
| v["--pm-font-mono"] = base["font"]["mono"] | ||
| # registry-sourced accents | ||
| acc = theme["accents"] | ||
| primary = _agent(reg, acc["primary"]); secondary = _agent(reg, acc["secondary"]); sig = _agent(reg, acc["signature"]) | ||
| v["--pm-accent"] = _field(primary, "color", acc["primary"]) | ||
| v["--pm-accent-soft"] = _field(primary, "accent", acc["primary"]) | ||
| v["--pm-accent-2"] = _field(secondary, "color", acc["secondary"]) | ||
| v["--pm-signature"] = _field(sig, "color", acc["signature"]) | ||
| # theme overrides last | ||
| for k, val in theme.get("overrides", {}).get("color", {}).items(): | ||
| v[f"--pm-{k}"] = val | ||
| # guard every emitted value against CSS/TS injection (silent-failure trap) | ||
| return {k: _safe(k, val) for k, val in v.items()} | ||
|
|
||
| DEFAULT_THEME = "pmoves-armor" | ||
|
|
||
| def emit_css(theme: dict, vars_: dict) -> str: | ||
| name = theme["name"] | ||
| sel = f'[data-theme="{name}"]' | ||
| if name == DEFAULT_THEME: | ||
| sel += ", :root:not([data-theme])" | ||
| lines = [f"/* generated by pmoves/design/generate.py — do not edit */", | ||
| f":root{sel} {{"] | ||
| for k in sorted(vars_): | ||
| lines.append(f" {k}: {vars_[k]};") | ||
| lines.append("}") | ||
| return "\n".join(lines) + "\n" | ||
|
|
||
| def emit_ts(themes: dict) -> str: | ||
| body = json.dumps(themes, indent=2, sort_keys=True) | ||
| return ("// generated by pmoves/design/generate.py — do not edit\n" | ||
| f"export const themes = {body} as const;\n" | ||
| "export type ThemeName = keyof typeof themes;\n") | ||
|
|
||
| def main() -> int: | ||
| reg = load_registry() | ||
| out = DESIGN / "build"; out.mkdir(exist_ok=True) | ||
| resolved = {} | ||
| for tf in sorted((DESIGN / "themes").glob("*.json")): | ||
| theme = json.loads(tf.read_text(encoding="utf-8")) | ||
| vars_ = resolve_theme(theme, reg) | ||
| resolved[theme["name"]] = vars_ | ||
| (out / f"tokens.{theme['name']}.css").write_text(emit_css(theme, vars_), encoding="utf-8") | ||
| (out / "tokens.ts").write_text(emit_ts(resolved), encoding="utf-8") | ||
| print(f"generated {len(resolved)} themes -> {out}") | ||
| return 0 | ||
|
|
||
| if __name__ == "__main__": | ||
| raise SystemExit(main()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| /* pmoves/design/preview.css */ | ||
| body { background: var(--pm-bg); color: var(--pm-ink); font-family: var(--pm-font-body); margin: 0; padding: var(--pm-space-xl); } | ||
| h1 { font-family: var(--pm-font-display); } | ||
| .cta { background: linear-gradient(135deg, var(--pm-accent), var(--pm-accent-2)); color: #fff; border: 0; padding: 10px 16px; border-radius: var(--pm-radius); cursor: pointer; } | ||
| .swatches { display: flex; gap: 12px; margin: 24px 0; } | ||
| .sw { width: 120px; height: 64px; border-radius: var(--pm-radius); display: flex; align-items: end; padding: 6px; font-size: 11px; color: #fff; border: 1px solid var(--pm-border-subtle); } | ||
| .sw[data-var="--pm-accent"]{background:var(--pm-accent)} .sw[data-var="--pm-accent-2"]{background:var(--pm-accent-2)} | ||
| .sw[data-var="--pm-signature"]{background:var(--pm-signature)} .sw[data-var="--pm-surface"]{background:var(--pm-surface)} | ||
| .motifs{display:flex;gap:20px;align-items:center} | ||
| .shard{width:0;height:0;border-left:14px solid transparent;border-right:14px solid transparent;border-bottom:24px solid var(--pm-accent-2)} | ||
| .hex{width:26px;height:23px;background:var(--pm-surface);border:1px solid var(--pm-accent);clip-path:polygon(25% 0,75% 0,100% 50%,75% 100%,25% 100%,0 50%)} | ||
| .star{font-size:28px;color:var(--pm-signature)} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| <!DOCTYPE html> | ||
| <html lang="en" data-theme="pmoves-armor"> | ||
| <head> | ||
| <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"> | ||
| <title>PMOVES Design Tokens — Preview</title> | ||
| <link rel="stylesheet" href="./build/tokens.pmoves-armor.css"> | ||
| <link rel="stylesheet" href="./build/tokens.darkxside-skin.css"> | ||
| <link rel="stylesheet" href="./preview.css"> | ||
| </head> | ||
| <body> | ||
| <header><h1>PMOVES Design Tokens</h1> | ||
| <button id="toggle" class="cta">Toggle armor / skin</button></header> | ||
| <section class="swatches"> | ||
| <span class="sw" data-var="--pm-accent">accent</span> | ||
| <span class="sw" data-var="--pm-accent-2">accent-2</span> | ||
| <span class="sw" data-var="--pm-signature">✦ signature</span> | ||
| <span class="sw" data-var="--pm-surface">surface</span> | ||
| </section> | ||
| <section class="motifs"> | ||
| <span class="shard"></span><span class="hex"></span><span class="star">✦</span> | ||
| </section> | ||
| <script type="module" src="./preview.js"></script> | ||
| </body> | ||
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| // pmoves/design/preview.js | ||
| import { toggleTheme } from "./theme-provider.js"; | ||
| document.getElementById("toggle").addEventListener("click", () => toggleTheme()); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.