Skip to content

Commit 6de4b0f

Browse files
lbliiiclaude
andauthored
release: prepare v0.6.0 — version bump, changelog, docs update (#93)
Bump version to 0.6.0, add changelog and release notes for component framework (#90), kida readme CLI (#89), and sharp-edge fixes (#91, #92). Update all doc version references from 0.5.0 to 0.6.0. Add documentation for three new CLI commands (components, readme, extract), strict_undefined and jinja2_compat_warnings config options, DefMetadata/DefParamInfo introspection API, component call stack in errors, compile-time warnings, and enhanced UndefinedError with kind field. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6bdc245 commit 6de4b0f

13 files changed

Lines changed: 496 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.6.0] - 2026-04-13
11+
12+
### Added
13+
14+
- **Component framework** — Def introspection API (`DefMetadata`, `DefParamInfo`, `list_defs`, `def_metadata`), component call stack in error reporting, `kida components` CLI command, type-aware prop validation with `TypeMismatch` diagnostics, Components Guide and Jinja2-vs-Kida comparison docs. (#90)
15+
- **`kida readme` CLI** — Auto-generates README.md from project metadata (pyproject.toml, filesystem, git). Ships 4 preset templates (default, minimal, library, cli) with `--set` overrides and `--json` debug mode. (#89)
16+
17+
### Fixed
18+
19+
- **Sandbox `max_output_size` enforcement** — Was declared but never checked; now enforced. (#91)
20+
- **Block capture rejection**`{% set x %}...{% endset %}` block capture rejected at parse time. (#91)
21+
- **Error codes for all SecurityError raises** — K-SEC-001 through K-SEC-005. (#91)
22+
- **Autoescape validation at construction** — Invalid autoescape mode now rejected at `Environment()` creation, not first compile. (#91)
23+
- **Structured errors** — All bare `RuntimeError` raises replaced with `TemplateRuntimeError`. (#91)
24+
- **Optional chaining display**`?.` now renders `""` instead of `"None"` in display context. (#91)
25+
- **Broken `except` clauses** — Fixed 32 `except X, Y:` clauses (Python 2 syntax that only catches the first type). (#92)
26+
- **`render_with_blocks()` validation** — Unknown block names now raise with did-you-mean suggestions. (#92)
27+
- **Unknown compiler node types** — Now raises instead of silently ignoring. (#92)
28+
- **CLI check error reporting** — Errors no longer swallowed silently. (#92)
29+
- **`_Undefined.get()` API** — Fixed to match expected semantics. (#92)
30+
31+
### Changed
32+
33+
- **New warnings**`PrecedenceWarning` (K-WARN-001) for `?? + |` without parens, `CoercionWarning` for silent filter type coercion, `MigrationWarning` (K-WARN-002) for `set` scoping with `jinja2_compat_warnings` flag. (#91)
34+
- **`strict_undefined` mode** — Opt-in `Environment(strict_undefined=True)` for catching attribute typos. (#92)
35+
- **`UndefinedError` context** — Attribute/key lookups now say "Undefined attribute/key" instead of "Undefined variable". (#92)
36+
- **ErrorCode coverage** — Pushed from 21 to 73+ raise sites with full docs for all 38 ErrorCode values. (#91)
37+
1038
## [0.5.0] - 2026-04-12
1139

1240
### Added

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "kida-templates"
7-
version = "0.5.0"
7+
version = "0.6.0"
88
description = "Python component framework for HTML — typed props, named slots, scoped state, error boundaries, zero JavaScript"
99
readme = "README.md"
1010
requires-python = ">=3.14"

site/content/docs/advanced/sandbox.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ env = SandboxedEnvironment(sandbox_policy=policy)
7171
| `allow_import` | `bool` | `False` | Whether `__import__` is available in templates. |
7272
| `allow_mutating_methods` | `bool` | `False` | Whether mutating collection methods (`append`, `pop`, `clear`, etc.) are accessible. |
7373
| `allow_calling` | `frozenset[str] \| None` | `None` | Set of type names whose instances may be called. `None` permits all callables. An empty `frozenset()` blocks all calls. |
74-
| `max_output_size` | `int \| None` | `None` | Maximum render output length in characters. `None` means unlimited. |
74+
| `max_output_size` | `int \| None` | `None` | Maximum render output length in characters. Raises `SecurityError` (K-SEC-005) when exceeded. `None` means unlimited. |
7575
| `max_range` | `int` | `10000` | Maximum `range()` size allowed in templates. |
7676

7777
`SandboxPolicy` is a frozen dataclass -- construct a new instance for each configuration.

site/content/docs/get-started/installation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ uv sync
6767

6868
```python
6969
import kida
70-
print(kida.__version__) # 0.5.0
70+
print(kida.__version__) # 0.6.0
7171
```
7272

7373
Or from the command line:

site/content/docs/reference/api.md

Lines changed: 104 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ env = Environment(
4242
| `fragment_cache_size` | `int` | `1000` | Max cached fragments |
4343
| `fragment_ttl` | `float` | `300.0` | Fragment TTL (seconds) |
4444
| `static_context` | `dict \| None` | `None` | Values for compile-time partial evaluation |
45+
| `strict_undefined` | `bool` | `False` | Raise on missing attribute access |
46+
| `jinja2_compat_warnings` | `bool` | `False` | Warn on `{% set %}` scoping differences |
47+
| `validate_calls` | `bool` | `False` | Validate `{% def %}` call sites at compile time |
4548

4649
### Methods
4750

@@ -209,7 +212,7 @@ layout = env.get_template("_layout.html")
209212
html = layout.render_with_blocks({"content": inner_html}, title="Page")
210213
```
211214

212-
Each key in `block_overrides` names a block; the value is a pre-rendered HTML string.
215+
Each key in `block_overrides` names a block; the value is a pre-rendered HTML string. Unknown block names now raise `TemplateRuntimeError` with did-you-mean suggestions.
213216

214217
#### render_block_stream_async(block_name, **context)
215218

@@ -231,7 +234,62 @@ blocks = template.list_blocks()
231234
# ['title', 'nav', 'content', 'footer']
232235
```
233236

234-
### Introspection (frameworks, build systems)
237+
### Component Introspection
238+
239+
#### list_defs()
240+
241+
List all `{% def %}` component names in the template.
242+
243+
```python
244+
names = template.list_defs()
245+
# ['card', 'nav_link', 'badge']
246+
```
247+
248+
#### def_metadata()
249+
250+
Return metadata for all `{% def %}` components in the template. Returns a `dict[str, DefMetadata]`.
251+
252+
```python
253+
meta = template.def_metadata()
254+
card = meta["card"]
255+
print(card.name) # "card"
256+
print(card.template_name) # "components/card.html"
257+
print(card.lineno) # 3
258+
print(card.params) # (DefParamInfo(name='title', annotation='str', ...), ...)
259+
print(card.slots) # ('actions', 'footer')
260+
print(card.has_default_slot) # True
261+
```
262+
263+
**DefMetadata** fields:
264+
265+
| Field | Type | Description |
266+
|-------|------|-------------|
267+
| `name` | `str` | Component name |
268+
| `template_name` | `str \| None` | Source template |
269+
| `lineno` | `int` | Line number of `{% def %}` |
270+
| `params` | `tuple[DefParamInfo, ...]` | Parameter metadata |
271+
| `slots` | `tuple[str, ...]` | Named slot names |
272+
| `has_default_slot` | `bool` | Whether `{% slot %}` (unnamed) exists |
273+
274+
**DefParamInfo** fields:
275+
276+
| Field | Type | Description |
277+
|-------|------|-------------|
278+
| `name` | `str` | Parameter name |
279+
| `annotation` | `str \| None` | Type annotation (e.g. `"str"`, `"int"`) |
280+
| `has_default` | `bool` | Whether a default value is defined |
281+
| `is_required` | `bool` | `True` if no default value |
282+
283+
#### warnings
284+
285+
Compile-time warnings collected during template compilation.
286+
287+
```python
288+
for w in template.warnings:
289+
print(w.code, w.message, w.lineno)
290+
```
291+
292+
### Template Introspection (frameworks, build systems)
235293

236294
#### template_metadata()
237295

@@ -299,6 +357,7 @@ template.is_cacheable() # True only if all blocks cacheable
299357
| `name` | `str \| None` | Template name |
300358
| `filename` | `str \| None` | Source filename |
301359
| `is_async` | `bool` | `True` if template uses `{% async for %}` or `{{ await }}` |
360+
| `warnings` | `list[TemplateWarning]` | Compile-time warnings (precedence, coercion, migration) |
302361

303362
> **Note**: Calling `render()` or `render_stream()` on a template where `is_async` is `True` raises `TemplateRuntimeError`. Use `render_stream_async()` instead.
304363
@@ -499,7 +558,7 @@ Classify block metadata into framework roles: `"fragment"`, `"page_root"`, or `N
499558

500559
### TemplateError
501560

502-
Base class for all template errors.
561+
Base class for all template errors. All Kida exceptions carry an `ErrorCode` accessible via `exc.code`.
503562

504563
### TemplateSyntaxError
505564

@@ -514,9 +573,24 @@ except TemplateSyntaxError as e:
514573
print(e)
515574
```
516575

576+
### TemplateRuntimeError
577+
578+
Error during template rendering. Includes `component_stack` for errors inside `{% def %}` components:
579+
580+
```python
581+
from kida import TemplateRuntimeError
582+
583+
try:
584+
template.render(items=None)
585+
except TemplateRuntimeError as e:
586+
print(e.code) # ErrorCode enum value
587+
print(e.component_stack) # [(def_name, lineno, template_name), ...]
588+
print(e.format_compact()) # Formatted error with source snippet
589+
```
590+
517591
### TemplateNotFoundError
518592

519-
Template file not found.
593+
Template file not found. Includes caller context (requesting template and line number).
520594

521595
```python
522596
from kida import TemplateNotFoundError
@@ -529,17 +603,41 @@ except TemplateNotFoundError as e:
529603

530604
### UndefinedError
531605

532-
Accessing undefined variable.
606+
Accessing undefined variable or attribute. The `kind` field distinguishes between variable, attribute, and key lookups.
533607

534608
```python
535609
from kida import UndefinedError
536610

537611
try:
538612
env.from_string("{{ missing }}").render()
539613
except UndefinedError as e:
540-
print(e)
614+
print(e.kind) # "variable", "attribute", or "key"
615+
print(e) # "Undefined variable 'missing' in <string>:1"
541616
```
542617

618+
With `strict_undefined=True`, attribute access errors also include component context:
619+
620+
```python
621+
env = Environment(strict_undefined=True)
622+
# "Undefined attribute 'typo' on User object in page.html:5"
623+
```
624+
625+
### SecurityError
626+
627+
Raised by `SandboxedEnvironment` when a template violates the security policy. Carries error codes K-SEC-001 through K-SEC-005.
628+
629+
### Warning Classes
630+
631+
Compile-time warnings emitted during template compilation:
632+
633+
| Class | Code | Description |
634+
|-------|------|-------------|
635+
| `PrecedenceWarning` | K-WARN-001 | `\|` binds tighter than `??` |
636+
| `CoercionWarning` || Silent type coercion in filters (e.g. `"abc" \| float``0.0`) |
637+
| `MigrationWarning` | K-WARN-002 | `{% set %}` scoping differs from Jinja2 |
638+
639+
These are standard Python warnings and can be filtered with `warnings.filterwarnings`.
640+
543641
---
544642

545643
## Markup

0 commit comments

Comments
 (0)