Skip to content

Commit 8c16851

Browse files
lbliiiclaude
andauthored
chore(deps): upgrade kida-templates 0.6.0 → 0.7.0 + adapt to strict_undefined (#242)
* chore(deps): upgrade kida-templates 0.6.0 → 0.7.0 + adapt templates to strict_undefined Kida 0.7.0 flips `strict_undefined` to default-True, so missing attribute/key access now raises UndefinedError instead of silently rendering empty. Bengal's default theme relied on the lenient behavior in ~9 templates; each is now updated to use `?.` optional chaining with `?? default` fallbacks. Also fixed the `| groupby` iteration pattern in tiles.html — kida 0.7.0's groupby returns `{grouper, list}` dicts rather than `(key, items)` tuples. Three tests were updated for the new strict semantics (with-as nil-resilience now needs `?? none`, `{% do %}` was removed in favor of `{% set _ = ... %}` / `| compact`, and the variant-attribute regex matches the new safe-access form). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore(changelog): add fragment for kida 0.7.0 upgrade Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(theme): guard child menu item access in base.html submenu loop Kida 0.7.0 strict_undefined raised K-RUN-001 on `child.active` in the submenu loop at base.html:75 when menu items came from dict-shaped config/auto-nav sources that lack optional keys (active, icon). The outer `item` in render_menu_item already used the safe `item?.X ?? ...` pattern (L47); this extends it to the inner `child` loop. Caught by warm-build/cascade + i18n + autodoc integration tests on CI. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 0c49723 commit 8c16851

14 files changed

Lines changed: 92 additions & 71 deletions

File tree

bengal/themes/default/templates/base.html

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
_path, kind, tags are always defined - no defensive checks needed.
1010
================================================================================
1111
#}
12-
{# Template-wide variables using {% let %} for explicit scope #}
12+
{# Template-wide variables using {% let %} for explicit scope
13+
NOTE: `page` can be None in some render contexts (e.g., error shells, test fixtures).
14+
Kida 0.7.0 strict_undefined requires guarded access. #}
1315
{% let params = params ?? {} %}
14-
{% let _page_title = page.title %}
15-
{% let _page_url = page._path %}
16+
{% let _page_title = page?.title ?? none %}
17+
{% let _page_url = page?._path ?? none %}
1618

1719
{# Navigation - cache function calls #}
1820
{% let _current_lang = current_lang() %}
@@ -23,16 +25,16 @@
2325
{% let _build_badge = site.build_badge %}
2426
{% let _doc_app = site.document_application %}
2527
{% let _link_previews = site.link_previews %}
26-
{% let _per_page_json = 'json' in (config.output_formats.per_page ?? []) %}
27-
{% let _per_page_md = 'markdown' in (config.output_formats.per_page ?? []) %}
28+
{% let _per_page_json = 'json' in ((config?.output_formats ?? {})?.per_page ?? []) %}
29+
{% let _per_page_md = 'markdown' in ((config?.output_formats ?? {})?.per_page ?? []) %}
2830

2931
{# Derived values #}
3032
{% let _main_menu = get_menu_lang('main', _current_lang) %}
3133
{% let _footer_menu = get_menu_lang('footer', _current_lang) %}
3234

33-
{% let _doc_app_nav = _doc_app?.navigation %}
34-
{% let _view_transitions = _doc_app.enabled and _doc_app_nav?.view_transitions %}
35-
{% let _transition_style = _doc_app_nav?.transition_style %}
35+
{% let _doc_app_nav = _doc_app?.navigation ?? {} %}
36+
{% let _view_transitions = (_doc_app?.enabled ?? false) and (_doc_app_nav?.view_transitions ?? false) %}
37+
{% let _transition_style = _doc_app_nav?.transition_style ?? none %}
3638

3739
{# Auto navigation if main menu is empty #}
3840
{% let _auto_nav = get_auto_nav() if _main_menu | length == 0 else [] %}
@@ -42,7 +44,7 @@
4244
============================================================================= #}
4345
{% def render_menu_item(item, is_mobile=false) %}
4446
{% let has_children = item.children | length > 0 %}
45-
{% let is_active = item.active or (_page_url == item.href) %}
47+
{% let is_active = (item?.active ?? false) or (_page_url == item?.href) %}
4648
{% let is_trail = item.active_trail ?? false %}
4749

4850
<li
@@ -70,12 +72,12 @@
7072
{% if has_children %}
7173
<ul class="submenu">
7274
{% for child in item.children %}
73-
<li class="{{ ['active' if child.active] | classes }}">
74-
<a href="{{ child.href | absolute_url }}">
75-
{% with child.icon as icon_name %}
75+
<li class="{{ ['active' if (child?.active ?? false)] | classes }}">
76+
<a href="{{ (child?.href ?? '') | absolute_url }}">
77+
{% with (child?.icon ?? none) as icon_name %}
7678
{% if icon_name %}<span class="submenu-icon">{{ icon(icon_name, size=16) }}</span>{% end %}
7779
{% end %}
78-
<span class="submenu-text"><bdi>{{ child.name }}</bdi></span>
80+
<span class="submenu-text"><bdi>{{ child?.name ?? '' }}</bdi></span>
7981
</a>
8082
</li>
8183
{% end %}
@@ -110,7 +112,7 @@
110112
{# Title - captured for reuse in Open Graph and Twitter Card #}
111113
{% capture page_title %}
112114
{% block title %}
113-
{% match page.title %}
115+
{% match page?.title ?? none %}
114116
{% case title if title and _site_title %}{{ title }} - {{ _site_title }}
115117
{% case title if title %}{{ title }}
116118
{% case _ %}{{ _site_title }}
@@ -126,21 +128,23 @@
126128
<meta name="description" content="{{ meta_desc | trim }}">
127129

128130
{# Meta Keywords - use page.keywords or fallback to tags #}
129-
{% if page.keywords %}
131+
{% if page?.keywords ?? none %}
130132
<meta name="keywords" content="{{ page.keywords | join(', ') }}">
131-
{% elif page.tags %}
133+
{% elif page?.tags ?? none %}
132134
<meta name="keywords" content="{{ page.tags | meta_keywords(10) }}">
133135
{% end %}
134136

135137
{# Robots directive for hidden/unlisted pages #}
136-
{% if page.robots_meta and page.robots_meta != 'index, follow' %}
138+
{% if (page?.robots_meta ?? none) and page.robots_meta != 'index, follow' %}
137139
<meta name="robots" content="{{ page.robots_meta }}">
138140
{% end %}
139141

140142
{# Content Signals — per-page AI directives (only when restricted) #}
141-
{% if not page.in_ai_train or not page.in_ai_input %}
142-
<meta name="content-signal:ai-train" content="{{ 'yes' if page.in_ai_train else 'no' }}">
143-
<meta name="content-signal:ai-input" content="{{ 'yes' if page.in_ai_input else 'no' }}">
143+
{% let _ai_train = page?.in_ai_train ?? true %}
144+
{% let _ai_input = page?.in_ai_input ?? true %}
145+
{% if not _ai_train or not _ai_input %}
146+
<meta name="content-signal:ai-train" content="{{ 'yes' if _ai_train else 'no' }}">
147+
<meta name="content-signal:ai-input" content="{{ 'yes' if _ai_input else 'no' }}">
144148
{% end %}
145149

146150
{# Open Graph / Facebook #}
@@ -172,10 +176,10 @@
172176
<meta name="bengal:search_preload" content="{{ config.search_preload ?? 'smart' }}">
173177
<meta name="bengal:baseurl" content="{{ site.baseurl }}">
174178
<meta name="bengal:index_url" content="{{ '/index.json' | absolute_url }}">
175-
{% if bengal.capabilities.prebuilt_search %}
179+
{% if bengal?.capabilities?.prebuilt_search ?? false %}
176180
<meta name="bengal:search_index_url" content="{{ '/search-index.json' | absolute_url }}">
177181
{% end %}
178-
{% if page.version %}
182+
{% if page?.version ?? none %}
179183
<meta name="bengal:version" content="{{ page.version }}">
180184
{% end %}
181185

@@ -185,7 +189,7 @@
185189
{% end %}
186190

187191
{# RSS Feed #}
188-
{% with config.i18n as i18n %}
192+
{% with (config?.i18n ?? none) as i18n %}
189193
{% let rss_href = '/rss.xml' %}
190194
{% if i18n and i18n.strategy == 'prefix' %}
191195
{% let default_lang = i18n.default_language ?? 'en' %}
@@ -220,7 +224,7 @@
220224
<link rel="preconnect" href="https://d3js.org" crossorigin>
221225

222226
{# Fonts #}
223-
{% if config.fonts %}
227+
{% if config?.fonts ?? none %}
224228
<link rel="preload" href="{{ asset_url('fonts/outfit-700.woff2') }}" as="font" type="font/woff2" crossorigin>
225229
<link rel="preload" href="{{ asset_url('fonts.css') }}" as="style" onload="this.onload=null;this.rel='stylesheet'">
226230
<noscript>
@@ -292,8 +296,8 @@
292296

293297
</head>
294298

295-
<body data-type="{{ page.type }}" data-variant="{{ page.variant or '' }}"
296-
class="page-kind-{{ page.kind or 'page' }}{% if page.is_draft %} draft-page{% end %}{% if page.hidden %} hidden-page{% end %}{% if page.is_featured %} featured-content{% end %}">
299+
<body data-type="{{ page?.type ?? '' }}" data-variant="{{ (page?.variant ?? '') or '' }}"
300+
class="page-kind-{{ (page?.kind ?? none) or 'page' }}{% if page?.is_draft ?? false %} draft-page{% end %}{% if page?.hidden ?? false %} hidden-page{% end %}{% if page?.is_featured ?? false %} featured-content{% end %}">
297301

298302
{% if 'accessibility.skip_link' in (theme.features ?? []) %}
299303
<a href="#main-content" class="skip-link">Skip to main content</a>
@@ -328,7 +332,7 @@
328332
</li>
329333
{% end %}
330334
{% end %}
331-
{% with site.params.repo_url as repo_url %}
335+
{% with (site?.params?.repo_url ?? none) as repo_url %}
332336
{% if repo_url and not (site._dev_menu_metadata?.github_bundled ?? false) %}
333337
<li><a href="{{ repo_url }}" target="_blank" rel="noopener" aria-label="GitHub">{{
334338
icon('github-logo', size=16) }} <span>GitHub</span></a></li>
@@ -340,8 +344,8 @@
340344
{% end %}
341345

342346
<div class="header-actions hidden-mobile">
343-
{% with config.search.ui as ui %}
344-
{% if ui.modal ?? false %}
347+
{% with (config?.search?.ui ?? none) as ui %}
348+
{% if ui and (ui?.modal ?? false) %}
345349
<button type="button" class="nav-search-trigger" id="nav-search-trigger" title="Search (⌘K)">{{
346350
icon('magnifying-glass', size=16) }} <span>Search</span> <kbd
347351
class="nav-search-shortcut">⌘K</kbd></button>
@@ -366,8 +370,8 @@
366370
{# Search Modal Block — placed after <main> so content starts early in DOM #}
367371
{% block site_search_modal %}
368372
{% cache 'base-search-modal-' ~ _current_lang ~ '-' ~ (bengal.build.timestamp ?? '') %}
369-
{% with config.search.ui as ui %}
370-
{% if ui.modal ?? false %}
373+
{% with (config?.search?.ui ?? none) as ui %}
374+
{% if ui and (ui?.modal ?? false) %}
371375
<dialog id="search-modal" class="search-modal"
372376
aria-label="{{ t('search.aria_label', default='Search documentation') }}">
373377
<div class="search-modal__backdrop" data-close-modal></div>
@@ -462,7 +466,7 @@
462466
<a href="{{ item.href | absolute_url }}">{{ item.name }}</a>
463467
</li>{% end %}
464468
{% end %}
465-
{% with site.params.repo_url as repo_url %}
469+
{% with (site?.params?.repo_url ?? none) as repo_url %}
466470
{% if repo_url and not (site._dev_menu_metadata?.github_bundled ?? false) %}
467471
<li><a href="{{ repo_url }}" target="_blank" rel="noopener">{{ icon('github-logo', size=16) }}
468472
GitHub</a></li>
@@ -589,7 +593,7 @@
589593
{% end %}{# /cache base-speculation #}
590594

591595
{# Bootstrap metadata — moved after content for faster content-start position #}
592-
{% if config.expose_metadata_json %}
596+
{% if config?.expose_metadata_json ?? false %}
593597
<script id="bengal-bootstrap" type="application/json">{{ bengal | tojson }}</script>
594598
<script>
595599
(function () {

bengal/themes/default/templates/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@
3434
<header class="section-header">
3535
<h1>{{ page.title if page else section.title if section else site.title }}</h1>
3636

37-
{% if page and params.description %}
37+
{% if page and (params?.description ?? none) %}
3838
<p class="section-description">{{ params.description }}</p>
39-
{% elif section and section.params.description %}
39+
{% elif section and (section?.params?.description ?? none) %}
4040
<p class="section-description">{{ section.params.description }}</p>
4141
{% end %}
4242
</header>

bengal/themes/default/templates/partials/action-bar.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
{% let breadcrumb_items = get_breadcrumbs(page) ?? [] %}
6767

6868
{% let params = params ?? {} %}
69-
{% let has_metadata = params?.author or page?.date or page?.content or params?.lastmod %}
69+
{% let has_metadata = (params?.author ?? none) or (page?.date ?? none) or (page?.content ?? none) or (params?.lastmod ?? none) %}
7070
{% let theme_features = theme?.features ?? [] %}
7171

7272
<div class="action-bar-container">
@@ -154,7 +154,7 @@
154154
{% if has_metadata %}
155155
<div id="action-bar-metadata-popover" popover class="action-bar-metadata--popover">
156156
<div class="action-bar-metadata-content">
157-
{% with params?.author as author %}
157+
{% with (params?.author ?? none) as author %}
158158
{% if author and 'content.author' in theme_features %}
159159
<span class="action-bar-meta-item">
160160
{{ icon("user", size=16) }}
@@ -181,7 +181,7 @@
181181
{% end %}
182182
{% end %}
183183

184-
{% with params?.lastmod ?? page?.date as last_updated %}
184+
{% with (params?.lastmod ?? page?.date ?? none) as last_updated %}
185185
{% if last_updated %}
186186
<span class="action-bar-meta-item">
187187
{{ icon("arrow-clockwise", size=16) }}

bengal/themes/default/templates/partials/article-jsonld.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
-#}
1313

1414
{%- set article_enabled = (config.structured_data ?? {}).article ?? true %}
15-
{%- set is_doc = page.type == 'doc' or page.kind == 'doc' %}
16-
{%- set is_post = page.type == 'post' or page.kind == 'post' %}
15+
{%- set is_doc = (page?.type ?? '') == 'doc' or (page?.kind ?? '') == 'doc' %}
16+
{%- set is_post = (page?.type ?? '') == 'post' or (page?.kind ?? '') == 'post' %}
1717
{%- set _toc = page.toc_items ?? toc_items ?? [] %}
1818
{%- if article_enabled and (is_doc or is_post) %}
1919
<script type="application/ld+json">

bengal/themes/default/templates/partials/components/helpers.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
{% def for_each_item_excluding_current(items) %}
2424
{% let page_path = page?._path ?? '' %}
2525
{% for item in items %}
26-
{% if item?._path != page_path %}
26+
{% if (item?._path ?? '') != page_path %}
2727
{{ caller(item) }}
2828
{% end %}
2929
{% end %}

bengal/themes/default/templates/partials/components/tiles.html

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
'description': subsection?.metadata?.description ?? '',
4949
'weight': subsection?.weight ?? 999,
5050
'page_count': sub_pages | length,
51+
'date': none,
5152
'icon': subsection?.metadata?.icon ?? ''
5253
}) %}
5354
{% end %}
@@ -57,7 +58,7 @@
5758
{% if children %}
5859
{% for post in children %}
5960
{# Skip the current page #}
60-
{% if post?._path != page_path %}
61+
{% if (post?._path ?? '') != page_path %}
6162
{% let post_content = post?.content ?? '' %}
6263
{% let post_desc = post?.metadata?.description ?? '' %}
6364
{% let desc = post_desc ?? (post_content | strip_html | excerpt(120) if post_content else '') %}
@@ -69,7 +70,8 @@
6970
'href': post?.href ?? post?._path ?? '',
7071
'description': desc,
7172
'weight': post?.weight ?? 999,
72-
'date': post?.date,
73+
'page_count': 0,
74+
'date': post?.date ?? none,
7375
'icon': post?.metadata?.icon ?? ''
7476
}) %}
7577
{% end %}
@@ -108,7 +110,8 @@
108110
'href': rel_href,
109111
'description': desc,
110112
'weight': rel_page?.weight ?? 999,
111-
'date': rel_page?.date,
113+
'page_count': 0,
114+
'date': rel_page?.date ?? none,
112115
'icon': rel_page?.metadata?.icon ?? ''
113116
}) %}
114117
{% end %}
@@ -181,7 +184,9 @@ <h3 class="content-tile-card-title">
181184
{# ===== COMPACT VARIANT (default) ===== #}
182185
{% case _ %}
183186
<div class="content-tiles-list">
184-
{% for group_name, group_items in sorted_items | groupby('group') %}
187+
{% for group in sorted_items | groupby('group') %}
188+
{% let group_name = group.grouper %}
189+
{% let group_items = group.list %}
185190
{# Group headers when group_by='type' #}
186191
{% if group_by == 'type' and group_name == 'related' and related_title %}
187192
<div class="content-tiles-group-header">

bengal/themes/default/templates/partials/meta-generator.html

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,25 @@
66
<meta name="generator" content="{{ bengal.engine.name }} {{ bengal.engine.version }}">
77

88
{# Standard/Extended extras #}
9-
{% if bengal.theme.name %}
9+
{% let _theme = bengal?.theme ?? none %}
10+
{% if _theme and (_theme?.name ?? none) %}
1011
<meta name="bengal:theme"
11-
content="{{ bengal.theme.name }}{% if bengal.theme.version %}@{{ bengal.theme.version }}{% end %}">
12+
content="{{ _theme.name }}{% if _theme?.version ?? none %}@{{ _theme.version }}{% end %}">
1213
{% end %}
1314

1415
{# Suppress build timestamp in dev to avoid content churn and spurious reloads #}
15-
{% if (not config.dev_server) and bengal.build.timestamp %}
16-
<meta name="bengal:build" content="{{ bengal.build.timestamp }}">
16+
{% let _build = bengal?.build ?? none %}
17+
{% if (not (config?.dev_server ?? false)) and _build and (_build?.timestamp ?? none) %}
18+
<meta name="bengal:build" content="{{ _build.timestamp }}">
1719
{% end %}
1820

1921
{# Extended extras #}
20-
{% if bengal.rendering.markdown %}
22+
{% let _rendering = bengal?.rendering ?? none %}
23+
{% if _rendering and (_rendering?.markdown ?? none) %}
2124
<meta name="bengal:markdown"
22-
content="{{ bengal.rendering.markdown }}{% if bengal.rendering.markdownVersion %}@{{ bengal.rendering.markdownVersion }}{% end %}">
25+
content="{{ _rendering.markdown }}{% if _rendering?.markdownVersion ?? none %}@{{ _rendering.markdownVersion }}{% end %}">
2326
{% end %}
24-
{% if bengal.rendering.highlighter %}
27+
{% if _rendering and (_rendering?.highlighter ?? none) %}
2528
<meta name="bengal:highlighter"
26-
content="{{ bengal.rendering.highlighter }}{% if bengal.rendering.highlighterVersion %}@{{ bengal.rendering.highlighterVersion }}{% end %}">
29+
content="{{ _rendering.highlighter }}{% if _rendering?.highlighterVersion ?? none %}@{{ _rendering.highlighterVersion }}{% end %}">
2730
{% end %}

bengal/themes/default/templates/partials/page-hero/_macros.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ <h1 class="page-hero__title page-hero__title--code">
469469
{% if source_file and config?.github_repo %}
470470
{% let branch = config?.github_branch ?? 'main' %}
471471
{% let line_anchor = '#L' ~ element.line_number if element?.line_number else '' %}
472-
{% let source_url = config.params.github_repo ~ '/blob/' ~ branch ~ '/' ~ source_file ~ line_anchor %}
472+
{% let source_url = config.github_repo ~ '/blob/' ~ branch ~ '/' ~ source_file ~ line_anchor %}
473473
<a href="{{ source_url }}" class="page-hero__source-link" target="_blank" rel="noopener">
474474
{{ icon('file-code', size=14) }}
475475
<span>{{ t('source.view', default='View source') }}</span>
@@ -504,7 +504,7 @@ <h1 class="page-hero__title page-hero__title--code">
504504

505505
{% def hero_section(section, page, hero_context) %}
506506
{# Determine section type from explicit context, page type, or URL (fallback) #}
507-
{% let is_cli = hero_context?.is_cli or (page?.type == 'autodoc-cli') or (page?.href and '/cli' in page.href) or false
507+
{% let is_cli = (hero_context?.is_cli ?? false) or (page?.type == 'autodoc-cli') or (page?.href and '/cli' in page.href) or false
508508
%}
509509
{% let section_type = 'cli' if is_cli else 'api' %}
510510
{% let labels = SECTION_LABELS[section_type] %}

bengal/themes/default/templates/partials/product-jsonld.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
{% include "partials/product-jsonld.html" %}
3333
-#}
3434

35-
{%- if page.structured_data and page.type == 'product' %}
35+
{%- if (page?.structured_data ?? none) and (page?.type ?? '') == 'product' %}
3636
<script type="application/ld+json">
3737
{
3838
"@context": "https://schema.org/",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Upgrade kida-templates to 0.7.0. The new default `strict_undefined=True` now raises `UndefinedError` on missing attrs/keys instead of rendering empty — default-theme templates were updated to use `?.` optional chaining with `?? default` fallbacks, and `tiles.html` was updated for the new `groupby` iteration shape (`{grouper, list}` dicts instead of `(key, items)` tuples).

0 commit comments

Comments
 (0)