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
115 changes: 105 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ qtshadcn/
├── themes/
│ └── default.xml # Default light/dark token values
├── tokens/
│ ├── colors.py # alpha(), mix() helpers
│ ├── radius.py # class_px() helper
│ └── scale.py # spacing_px(), spacing_int(), TEXT_*, FONT_WEIGHT_* constants
│ ├── colors.py # withAlpha(), blendColors() helpers
│ ├── radius.py # resolveRadius() helper
│ └── scale.py # resolveSpacing(), toSpacingInt()
└── fonts/ # Bundled font files
examples/
└── gallery/ # Modular widget gallery with live theme editor
Expand Down Expand Up @@ -100,13 +100,107 @@ pre-commit run renovate-config-validator # validate Renovate config
- **Type annotations**: required on all public functions; `ty` must pass with no warnings
- **Tests**: no docstrings required in `tests/` or `examples/`

### Naming Conventions

> Ruff does not enforce naming style in this project (`N` rules are not selected). These rules are binding for all agents.

| Symbol | Style | Example |
| --- | --- | --- |
| Functions, methods, variables, parameters | `camelCase` | `resolveRadius`, `baseRadius` |
| Private functions / methods | `_camelCase` (single leading underscore) | `_parsePixel`, `_toPixelString` |
| Classes | `PascalCase` | `ShadcnTheme`, `ThemeMode` |
| Global constants | `UPPER_SNAKE_CASE` | `TEXT_SM`, `FONT_WEIGHT_BOLD` |

**No abbreviations.** Names must be self-explanatory without context.

- ❌ `addPx`, `calcBtn`, `ctx`, `val`, `r`
- ✅ `expandPixel`, `calculateButton`, `context`, `numericValue`, `baseRadius`

**Functions and methods must be verb + context** (at least two words).

- ❌ `process()`, `radius()`, `data()`
- ✅ `processInvoice()`, `resolveRadius()`, `fetchThemeData()`

The following example shows all rules in action:

```python
# Public: camelCase, verb + context, no abbreviations
def calculateTotalPrice(unitPrice: float, quantity: int) -> float: ...
def fetchUserProfile(userId: str) -> UserProfile: ...
def formatDisplayName(firstName: str, lastName: str) -> str: ...


# Private: _camelCase, same verb + context rule applies
def _parseRawResponse(responseBody: str) -> dict: ...
def _buildRequestPayload(eventType: str, metadata: dict) -> dict: ...
```

### Comment Style

- Comment the **WHY** or the **HOW**, never the obvious **WHAT** (e.g., never `# adds two numbers` above `a + b`)
- **Forbidden**: docstrings that just restate parameter names without real context
- **Forbidden**: exhaustive `Args: / Returns: / Raises:` blocks on trivial functions (getters/setters, simple wrappers, functions < 5 lines with obvious behavior)
- **Forbidden**: filler phrases (`"This function is responsible for..."`, `"Esta función se encarga de..."`)
- Only comment where the code doesn't explain itself: non-obvious decisions, workarounds, temporary hacks, trade-offs
- Adapt documentation depth to complexity — trivial functions need minimal docs, complex ones need full context

**Approved docstring format** (Google style — omit sections that add no value):

```python
def example(parametro1: int, parametro2: str = "default") -> str:
"""
Brief one-line summary of what the function does.

Longer description only when needed — context, behaviour, or edge cases
that the signature and body don't make obvious.

Args:
parametro1 (int): What it represents and why it matters, not just its name.
parametro2 (str, optional): When the default is surprising or has side-effects
worth noting (default: "default").

Returns:
str: What the return value means, not just its type.

Raises:
ValueError: Only when the condition that triggers it is non-obvious.

Examples:
>>> example(10)
'expected result'
"""
```

```python
class TrainingModel:
"""
Represents a machine learning model in training.

Attributes:
parametro1 (str): The identifier name of the model.
parametro2 (int): Number of epochs completed so far.
"""

def __init__(self, parametro1: str, parametro2: int = 0):
self.parametro1 = parametro1
self.parametro2 = parametro2
```

**When to include each section:**

- `Args:` — always for parameters with non-obvious meaning or constraints
- `Returns:` — when the return value semantics aren't clear from the type
- `Raises:` — only for exceptions that callers need to handle
- `Examples:` — for complex functions, non-obvious usage, or public API
- Longer description — only when the one-liner isn't sufficient

---

## Architecture Rules

- **Widget styles belong in `qtshadcn/styles/shadcn.jinja` only** — do not create new QSS files
- **No hardcoded colors or pixel values in the Jinja template** — use tokens and helpers exclusively
- **Token helpers** (`colors.alpha`, `colors.mix`, `radius.class_px`, `scale.spacing_px`) are the only way to transform token values
- **Token helpers** (`Colors.withAlpha`, `Colors.blendColors`, `radius.resolveRadius`, `Scale.resolveSpacing`) are the only way to transform token values
- **Theme tokens are defined in XML** (`themes/default.xml`) — do not add Python-level color constants
- **Public API surface**: `qsettings`, `ThemeMode`, `setThemeMode()`, `toggleThemeMode()`,
`themeMode()`, `isDarkTheme()`, `setTheme()`, `getTheme()`, `setStyleSheet()`,
Expand All @@ -121,32 +215,33 @@ Follows [semver](https://semver.org). Version is defined in `pyproject.toml` and
- `feat!:` commits → MAJOR bump
- `feat:` commits → MINOR bump
- `fix:` commits → PATCH bump

## Available Skills

Use these skills for detailed patterns on-demand:

### Generic Skills (Any Project)

| Skill | Description | URL |
|-------|-------------|-----|
| ------- | ------------- | ----- |
| `commit-hygiene` | Trigger: commit, git commit, conventional commit, branch cleanup. Enforce conventional commits and publishing approval for QtShadcn. | [SKILL.md](skills/commit-hygiene/SKILL.md) |
| `pytest` | Trigger: pytest tests, pytest coverage, fixtures, mocking, markers, parametrize, test discovery. Write idiomatic Python pytest tests and test helpers. | [SKILL.md](skills/pytest/SKILL.md) |
| `skill-creator` | Trigger: new skills, agent instructions, documenting AI usage patterns. Create LLM-first skills with valid frontmatter. | [SKILL.md](skills/skill-creator/SKILL.md) |
| `skill-improver` | Trigger: improve skills, audit skills, refactor skills, skill quality. Audit and upgrade existing LLM-first skills. | [SKILL.md](skills/skill-improver/SKILL.md) |

### QtShadcn Specific Skills

| Skill | Description | URL |
|-------|-------------|-----|
| `release-notes` | Trigger: release, release notes, changelog, tag, version. Run the QtShadcn release process: draft notes, verify version, create tag and GitHub Release. | [SKILL.md](skills/release-notes/SKILL.md) |
| `skill-sync` | Syncs skill metadata to AGENTS.md Available Skills sections. Trigger: When updating skill metadata (metadata.scope/metadata.auto_invoke), regenerating Available Skills tables, or running ./skills/skill-sync/assets/sync.py (including --dry-run/--scope). | [SKILL.md](skills/skill-sync/SKILL.md) |
| Skill | Description | URL |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------- |
| `release-notes` | Trigger: release, release notes, changelog, tag, version. Run the QtShadcn release process: draft notes, verify version, create tag and GitHub Release. | [SKILL.md](skills/release-notes/SKILL.md) |
| `skill-sync` | Syncs skill metadata to AGENTS.md Available Skills sections. Trigger: When updating skill metadata (metadata.scope/metadata.auto_invoke), regenerating Available Skills tables, or running ./skills/skill-sync/assets/sync.py (including --dry-run/--scope). | [SKILL.md](skills/skill-sync/SKILL.md) |

### Auto-invoke Skills

When performing these actions, ALWAYS invoke the corresponding skill FIRST:

| Action | Skill |
|--------|-------|
| -------- | ------- |
| After creating/modifying a skill | `skill-sync` |
| Auditing or improving existing skills | `skill-improver` |
| Checking pytest coverage or test discovery | `pytest` |
Expand Down
50 changes: 50 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,55 @@
# QtShadcn v0.4.0

## Changes

- Modernized theme engine internals.
- Modernized token helpers and added typography tokens.
- Updated default theme and QSS template.
- Consolidated font bundles with Inter, updated Roboto, and removed italic/condensed variants.
- Updated guides and examples.

## Verification

```bash
pip install qtshadcn==0.4.0
python -c "import qtshadcn; print(qtshadcn.__version__)"
make test
```

---

# QtShadcn v0.3.0

## Breaking Changes

- Removed `apply_theme()` and `get_theme()` from the public API; use `setTheme()` and `getTheme()` instead.
- Removed the internal `cache.py` module; theme caching is no longer part of the public contract.

## What's New

- Added a persistent config system via a `qsettings` singleton for storing theme preferences across sessions.
- Added a 3-concept stylesheet API with a full rendering pipeline (`setStyleSheet`, `getStyleSheet`, `setTheme`, `getTheme`).
- Added `SystemThemeListener` for automatic OS-level light/dark theme reactivity.
- Exported all new public symbols from the package root.

## Fixes

- Made `toggleThemeMode()` deterministic when no mode is set.
- Set light mode explicitly in XML theme tests to avoid environment-dependent failures.

## Verification

```bash
pip install qtshadcn==0.3.0
python -c "import qtshadcn; print(qtshadcn.__version__)"
make test
```

---

# QtShadcn v0.2.0


## Breaking Changes

- Migrated the Qt binding layer from the internal `qtshadcn/common/binding.py` shim to `qtpy>=2.4.0`.
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ To keep the project maintainable, follow these rules when making changes:

- **Widget styles belong in `qtshadcn/styles/shadcn.jinja` only** — do not create new QSS files.
- **No hardcoded colors or pixel values in the Jinja template** — use tokens and helpers exclusively.
- **Token helpers** (`colors.alpha`, `colors.mix`, `radius.class_px`, `scale.spacing_px`) are the only way to transform token values.
- **Token helpers** (`Colors.withAlpha`, `Colors.blendColors`, `radius.resolveRadius`, `Scale.resolveSpacing`) are the only way to transform token values.
- **Theme tokens are defined in XML** (`themes/default.xml`) — do not add Python-level color constants.
- **Public API surface**: `qsettings`, `ThemeMode`, `setThemeMode()`, `toggleThemeMode()`,
`themeMode()`, `isDarkTheme()`, `setTheme()`, `getTheme()`, `setStyleSheet()`,
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ A QtShadcn theme is a plain XML file with two palette sections:
<input>#e2e8f0</input>
<ring>#0f172a</ring>
<radius>8px</radius>
<font_family>system-ui, sans-serif</font_family>
<font_family>Open Sans</font_family>
<spacing>4px</spacing>
<card>#ffffff</card>
<card_foreground>#020617</card_foreground>
Expand Down
2 changes: 1 addition & 1 deletion docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ The example values below come from the packaged `default.xml` light palette. The
| `input` | `#e2e8f0` |
| `ring` | `#0f172a` |
| `radius` | `8px` |
| `font_family` | `system-ui, sans-serif` |
| `font_family` | `Open Sans` |
| `spacing` | `4px` |

---
Expand Down
6 changes: 3 additions & 3 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Save this as `my_theme.xml` next to your script:
<input>#e2e8f0</input>
<ring>#0f172a</ring>
<radius>8px</radius>
<font_family>system-ui, sans-serif</font_family>
<font_family>Open Sans</font_family>
<spacing>4px</spacing>
<card>#ffffff</card>
<card_foreground>#020617</card_foreground>
Expand All @@ -89,7 +89,7 @@ Save this as `my_theme.xml` next to your script:
<input>#1e293b</input>
<ring>#cbd5e1</ring>
<radius>8px</radius>
<font_family>system-ui, sans-serif</font_family>
<font_family>Open Sans</font_family>
<spacing>4px</spacing>
<card>#020617</card>
<card_foreground>#f8fafc</card_foreground>
Expand Down Expand Up @@ -143,7 +143,7 @@ tokens = getTheme()
QtShadcn includes **Open Sans** and **Roboto** under `qtshadcn/fonts/`. To activate them, set `font_family` in your XML:

```xml
<font_family>Open Sans, system-ui, sans-serif</font_family>
<font_family>Open Sans</font_family>
```

The fonts are registered automatically when the stylesheet is rendered.
Expand Down
51 changes: 16 additions & 35 deletions docs/theme-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,53 +48,34 @@ Every palette section must contain **all 22 tokens** listed below. If any are mi

---

## Supported Color Formats
## Supported Token Values

Token values can be expressed in any of the following formats. The runtime resolves them to `rgb()` or `rgba()` before injecting into QSS.
Color and size values are used directly in the generated QSS, so they must be Qt-compatible.

### Hex
### Colors

Use HEX colors:

```xml
<primary>#0f172a</primary>
```

### RGB / RGBA
`rgb()` and `rgba()` values are accepted as QSS-compatible passthrough values:

```xml
<primary>rgb(15, 23, 42)</primary>
<primary>rgba(15, 23, 42, 0.8)</primary>
```

### HSL

```xml
<primary>hsl(222 47% 11%)</primary>
<primary>hsl(222 47% 11% / 0.5)</primary>
<!-- alpha as percentage -->
<primary>hsl(222 47% 11% / 50%)</primary>
```

### OKLCH
### Sizes

```xml
<primary>oklch(13% 0.03 264)</primary>
<!-- with alpha -->
<primary>oklch(13% 0.03 264 / 0.8)</primary>
<!-- lightness as raw value (0–1) -->
<primary>oklch(0.13 0.03 264)</primary>
```

!!! note
OKLCH is converted to `rgb()` at parse time. QSS never sees the raw `oklch()` value.

### rem
Use pixel values for `radius` and `spacing`:

```xml
<radius>0.5rem</radius>
<radius>8px</radius>
<spacing>4px</spacing>
```

Converted to `px` using `1rem = 16px`. `0.5rem` becomes `8px`.

---

## Fonts
Expand All @@ -104,14 +85,14 @@ QtShadcn ships with **Open Sans** and **Roboto** under `qtshadcn/fonts/`. They a
To use them, reference the family name in your theme:

```xml
<font_family>Open Sans, system-ui, sans-serif</font_family>
<font_family>Open Sans</font_family>
```

```xml
<font_family>Roboto, system-ui, sans-serif</font_family>
<font_family>Open Sans</font_family>
```

The fallback chain (`system-ui, sans-serif`) ensures the app works even on systems where the fonts can't be loaded.
The fallback chain (`sans-serif`) ensures the app works even on systems where the fonts can't be loaded.

---

Expand All @@ -126,20 +107,20 @@ The fallback chain (`system-ui, sans-serif`) ensures the app works even on syste
<card_foreground>#020617</card_foreground>
<popover>#ffffff</popover>
<popover_foreground>#020617</popover_foreground>
<primary>oklch(13% 0.03 264)</primary>
<primary>#0f172a</primary>
<primary_foreground>#f8fafc</primary_foreground>
<secondary>#f1f5f9</secondary>
<secondary_foreground>#0f172a</secondary_foreground>
<accent>#f1f5f9</accent>
<accent_foreground>#0f172a</accent_foreground>
<muted>#f1f5f9</muted>
<muted_foreground>hsl(215 16% 47%)</muted_foreground>
<muted_foreground>#64748b</muted_foreground>
<destructive>#ef4444</destructive>
<destructive_foreground>#f8fafc</destructive_foreground>
<border>#e2e8f0</border>
<input>#e2e8f0</input>
<ring>#0f172a</ring>
<radius>0.5rem</radius>
<radius>8px</radius>
<font_family>Open Sans, system-ui, sans-serif</font_family>
<spacing>4px</spacing>
</light>
Expand Down
2 changes: 1 addition & 1 deletion examples/gallery/custom.jinja
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#background {
color: {{ tokens.foreground }};
background-color: {{ tokens.background }};
border-radius: {{ radius.class_px(tokens.radius, "xl") }};
border-radius: {{ radius.resolveRadius(tokens.radius, "xl") }};
}
2 changes: 1 addition & 1 deletion examples/gallery/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ def _refresh_editor_widgets(self) -> None:
]:
raw = palette.get(name, "0px")
try:
val = int(raw.replace("px", "").replace("rem", "").strip())
val = int(raw.replace("px", "").strip())
except ValueError:
val = 0
slider.blockSignals(True)
Expand Down
Loading
Loading