# Kida 0.8.0

URL: /kida/releases/0.8.0/
Section: releases
Description: Optional chaining soft-lands on Mappings, plus relative/alias template paths, component validation diagnostics, and a stability gate

---

> For a complete page index, fetch /kida/llms.txt.

# v0.8.0

**Released** 2026-04-24.

Kida 0.8.0 refines the v0.7 strict-by-default story with one focused semantic fix, then layers on the first batch of pre-1.0 stabilization work: `?.` / `?[...]` now soften on Mappings, template references get refactor-safe `./`, `../`, and `@alias/` forms, component call validation surfaces structured `K-CMP-*` diagnostics, and the compile hot path is noticeably faster. A new stability gate (`make verify-stability`) makes the release discipline runnable locally.

## Breaking

- **`?.` and `?[...]` soften on Mapping receivers** — Optional chaining now short-circuits missing keys to `None` on any `collections.abc.Mapping` receiver (dict, `MappingProxyType`, `ChainMap`, dict subclasses), mirroring Python's `dict.get(key)` idiom and the TS/Swift optional-chaining mental model. Object attribute misses on non-Mapping receivers still raise `UndefinedError` under `strict_undefined`; Sequence out-of-range on `?[i]` also still raises in strict mode.

  **Migration**: if you were relying on `?.` to raise on a missing dict key (the v0.7 behavior), drop the `?.` and use strict access `{{ user.nickname }}`, or use the `get` filter `{{ user | get("nickname") }}`. Code using the recommended `{{ x?.y ?? "" }}` pattern is unaffected.

  See [`docs/tutorials/upgrade-to-v0.8.md`]({{< relref "tutorials/upgrade-to-v0.8.md" >}}).

## Added

### Template references stop being path-coupled

- **Relative paths** — `{% include %}`, `{% extends %}`, `{% embed %}`, and `{% from ... import ... %}` now accept `./` and `../` paths that resolve against the calling template's directory. `{% include "./_card.html" %}` from `pages/about.html` resolves to `pages/_card.html`, and the same template works unchanged after moving `pages/` anywhere in the tree. Absolute names render byte-identical to before; path-traversal protection still fires on walks above every loader search root.
- **Namespace aliases** — `Environment(template_aliases={"components": "ui/components", "layouts": "ui/layouts"})` maps short `@name/` prefixes to root-relative subtrees so cross-cutting component libraries stop being path-coupled. Templates can write `{% include "@components/card.html" %}` regardless of where the caller lives. Unknown aliases raise `TemplateNotFoundError` with the list of configured aliases.
- **`kida check --lint-fragile-paths`** — Opt-in lint rule that flags cross-template references whose target lives in the same folder as the caller (e.g. `{% include "pages/card.html" %}` from `pages/about.html`) and suggests the refactor-safe `./card.html` form. References that already use `./`, `../`, or `@alias/` are ignored.
- **Refactor-safe templates tutorial** — `docs/tutorials/refactor-safe-templates.md` walks through when to use `./` / `../` vs `@alias/`, plus a step-by-step migration recipe using `ripgrep`.

### Component validation diagnostics

- **`ComponentWarning` + `K-CMP-001` / `K-CMP-002`** — Component call validation now carries a dedicated `ComponentWarning` class (exported from `kida`) and structured error codes (`COMPONENT_CALL_SIGNATURE`, `COMPONENT_TYPE_MISMATCH`) so downstream filters can target component contract violations specifically.
- **Cross-template call validation** — Literal `{% from "card.html" import card %}` followed by `{% call card(...) %}` validates arguments against the imported def's metadata, not just locally-defined defs.

### Optional-chaining upgrade kit

- **v0.8 upgrade tutorial** — `docs/tutorials/upgrade-to-v0.8.md` documents the `?.` / `?[...]` Mapping-soft semantics, the rationale (schema-less dicts vs. schema-ful objects), and the migration path.
- **v0.7 upgrade tutorial** — `docs/tutorials/upgrade-to-v0.7.md` collects the `strict_undefined=True` migration patterns: TL;DR, three fix patterns (`is defined`, `??`, `| default`), escape hatch, and null-safe idioms. Cross-linked from `README.md`, tutorials index, and the `UndefinedError` troubleshooting page.
- **Null-safe hint on `UndefinedError`** — Attribute/key errors now include a second `Hint:` line pointing users at `x?.y`, `x?.y ?? ""`, and `x | get("y", '')`. Wording reflects 0.8 semantics (`x?.y` alone is sufficient on Mappings).
- **Docs: optional chaining in `docs/syntax/variables.md`** — Dedicated `?.` and `?[...]` subsections with the Mapping-vs-object rule and a pointer to the v0.8 upgrade tutorial.
- **`getitem_preserve_none` / `strict_getitem_preserve_none`** — Runtime helpers powering `?[...]`. The compiler routes `?[key]` through these helpers instead of emitting direct subscript, giving Mapping-soft behavior under strict mode with no impact on None-receiver short-circuit.

### Agent UX

- **K-PAR-001 end-tag errors point at `kida check`** — Orphan `{% end %}`, mismatched closing tags, and typed-end mismatches now append a tip telling the reader to run `kida check <templates-dir> --strict` to surface every mismatch across the directory at once. Agents doing bulk template edits in downstream repos were discovering these one rendered route at a time.

### Stability gate

- **`make verify-stability`** — Runs lint, format, `ty`, full pytest with coverage gate, GIL-disabled render/sandbox/concurrency safety tests, wheel/sdist build, and a clean-venv package smoke test (`scripts/package_smoke.py`). `make verify-rc` remains an alias. New `docs/stability-gate.md` documents the pre-1.0 checklist.
- **Public API snapshot + diagnostics contract tests** — `tests/test_public_api_snapshot.py` and `tests/test_diagnostics_contract.py` lock down the exported surface and `ErrorCode` coverage so accidental renames/removals fail loudly.
- **Core benchmark regression probes** — `benchmarks/test_benchmark_regression_core.py` plus a new `scripts/benchmark_suites.sh` wrap the compile/render/stream/inherited-blocks/concurrent suites with consistent thresholds (fail on >5% compile/render/stream regression, >10% concurrency).

## Changed

- **Component call validation emits structured `K-CMP-*` diagnostics** — Existing call-signature and type-mismatch warnings now carry `K-CMP-001` / `K-CMP-002` codes and are filtered as `ComponentWarning` rather than generic `UserWarning`.

## Performance

- **Compile hot path trimmed** — Reduced per-template overhead in `compiler/core.py` and slimmed generated async compile output. The compiler hot path is the primary pytest-benchmark baseline going forward.
- **Dict attribute fast path** — `compiler/expressions.py` and `template/core.py` avoid redundant casts on Mapping attribute access.
- **Render scaffold bypass** — Render accumulator and scope management skip context-manager overhead on the fast path.
- **Sandbox output-limit cache** — `max_output_size` lookup is cached per-template rather than re-read on every render.

## Fixed

- **`from_string()` without `name=` warning dedups per-source** — The `UserWarning` fired when a bytecode cache is configured and `from_string()` is called without `name=` previously fired on every call (~1000× per test suite downstream). It now fires once per distinct source per `Environment`, keyed by `bytecode_cache.hash_source(source)`. First emission and text unchanged; two `Environment` instances warn independently; editing the template source re-arms the warning.

## Upgrade Notes

1. **Dict-shaped data gets cleaner** — `{{ config?.theme }}` on `config={}` used to raise; it now renders `""`. Any template that relied on the raise (rare) should switch to strict access `{{ config.theme }}` or `{{ config | get("theme") }}`.
2. **Object attributes are unchanged** — `{{ user?.nickname }}` on a `User()` instance without `.nickname` still raises in strict mode. That's the typo-catching win we're keeping.
3. **`?[i]` on lists is unchanged** — Sequence out-of-range still raises in strict mode. Off-by-one on a list is almost always a bug.
4. **Component warnings now filterable** — Downstream code that did `warnings.simplefilter("ignore", UserWarning)` to silence component contract warnings should narrow to `ComponentWarning` (or the `K-CMP-*` codes via the error-code API) to keep other signals intact.
5. **Move templates freely** — New `./`, `../`, and `@alias/` forms mean moving `pages/` no longer cascades into a grep-replace job. `kida check --lint-fragile-paths` finds existing same-folder-by-absolute-path references so you can migrate incrementally.
6. **Pin if needed** — `pip install 'kida-templates==0.7.*'` to stay on v0.7 semantics.

## Why this change

The v0.7 "receiver-only short-circuit" rule for `?.` was principled but out of step with every other language's mental model. TypeScript's `foo?.bar` returns `undefined` for missing property access — not because it "short-circuits the access," but because JS itself treats missing properties as `undefined`. Swift similarly has no "missing key raises" concept. Users reaching for `?.` in Kida expected TS/Swift semantics on dict-shaped data and got strict errors instead.

The v0.8 split respects both intuitions at once:

- **Dicts are schema-less** (config, JSON, kwargs). Missing keys are expected. `?.` → `None` matches `dict.get()`.
- **Objects have schemas.** A missing `.nickname` on a `User` object is almost always a typo. Strict mode still catches it.

You get typo protection where it matters (object attributes, list out-of-range), without the `?? ""` noise on every dict lookup.
