codetrail-cli 1.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. codetrail/__init__.py +3 -0
  2. codetrail/__main__.py +5 -0
  3. codetrail/agents/__init__.py +4 -0
  4. codetrail/agents/convert.py +92 -0
  5. codetrail/agents/export.py +284 -0
  6. codetrail/agents/instructions.py +42 -0
  7. codetrail/agents/protocol.py +111 -0
  8. codetrail/agents/tools.py +185 -0
  9. codetrail/assistant/__init__.py +161 -0
  10. codetrail/assistant/claude_code.py +330 -0
  11. codetrail/assistant/codex.py +260 -0
  12. codetrail/assistant/environment.py +66 -0
  13. codetrail/assistant/estimate.py +225 -0
  14. codetrail/assistant/fake.py +74 -0
  15. codetrail/assistant/guard.py +80 -0
  16. codetrail/assistant/guard_hook.py +58 -0
  17. codetrail/assistant/local.py +245 -0
  18. codetrail/assistant/local_tools.py +177 -0
  19. codetrail/assistant/prompts.py +404 -0
  20. codetrail/assistant/routing.py +86 -0
  21. codetrail/assistant/runner.py +120 -0
  22. codetrail/assistant/status.py +186 -0
  23. codetrail/assistant/usage.py +93 -0
  24. codetrail/bridge.py +292 -0
  25. codetrail/cli.py +407 -0
  26. codetrail/config.py +485 -0
  27. codetrail/database/__init__.py +60 -0
  28. codetrail/database/migrations/0001_facts.sql +34 -0
  29. codetrail/database/migrations/0002_settings.sql +5 -0
  30. codetrail/database/migrations/0003_learning.sql +34 -0
  31. codetrail/database/migrations/0004_assistant_usage.sql +24 -0
  32. codetrail/database/migrations/0005_page_failures.sql +7 -0
  33. codetrail/database/migrations/0006_metric_values.sql +9 -0
  34. codetrail/errors.py +5 -0
  35. codetrail/extract/__init__.py +249 -0
  36. codetrail/extract/adr.py +98 -0
  37. codetrail/extract/github_actions.py +140 -0
  38. codetrail/extract/openapi.py +101 -0
  39. codetrail/extract/python.py +221 -0
  40. codetrail/extract/swift.py +204 -0
  41. codetrail/extract/terraform.py +166 -0
  42. codetrail/extract/typescript.py +464 -0
  43. codetrail/facts/__init__.py +112 -0
  44. codetrail/facts/store.py +209 -0
  45. codetrail/generate/__init__.py +1 -0
  46. codetrail/generate/failures.py +31 -0
  47. codetrail/generate/outline.py +159 -0
  48. codetrail/generate/revise.py +72 -0
  49. codetrail/generate/run.py +530 -0
  50. codetrail/generate/scope.py +135 -0
  51. codetrail/generate/validate.py +218 -0
  52. codetrail/guide/__init__.py +147 -0
  53. codetrail/learn/__init__.py +143 -0
  54. codetrail/learn/routes.py +139 -0
  55. codetrail/locales/ar/LC_MESSAGES/codetrail.mo +0 -0
  56. codetrail/locales/ar/LC_MESSAGES/codetrail.po +2236 -0
  57. codetrail/locales/codetrail.pot +1942 -0
  58. codetrail/lock.py +114 -0
  59. codetrail/metrics/__init__.py +6 -0
  60. codetrail/metrics/activity.py +96 -0
  61. codetrail/metrics/churn.py +156 -0
  62. codetrail/metrics/commits.py +45 -0
  63. codetrail/metrics/coverage.py +114 -0
  64. codetrail/metrics/decisions.py +77 -0
  65. codetrail/metrics/history.py +47 -0
  66. codetrail/metrics/inventory.py +63 -0
  67. codetrail/metrics/rationale.py +74 -0
  68. codetrail/metrics/releases.py +26 -0
  69. codetrail/metrics/report.py +111 -0
  70. codetrail/metrics/repository.py +128 -0
  71. codetrail/metrics/size.py +95 -0
  72. codetrail/py.typed +0 -0
  73. codetrail/remove.py +50 -0
  74. codetrail/repo/__init__.py +4 -0
  75. codetrail/repo/git.py +56 -0
  76. codetrail/repo/gitleaks.toml +6 -0
  77. codetrail/repo/history.py +262 -0
  78. codetrail/repo/mirror.py +137 -0
  79. codetrail/repo/refresh.py +41 -0
  80. codetrail/repo/rules.py +83 -0
  81. codetrail/repo/secrets.py +145 -0
  82. codetrail/repo/signal.py +65 -0
  83. codetrail/repo/source.py +166 -0
  84. codetrail/search.py +178 -0
  85. codetrail/server.py +45 -0
  86. codetrail/system.py +484 -0
  87. codetrail/update.py +276 -0
  88. codetrail/web/__init__.py +1 -0
  89. codetrail/web/app.py +897 -0
  90. codetrail/web/diagrams.py +414 -0
  91. codetrail/web/documentation.py +146 -0
  92. codetrail/web/i18n.py +44 -0
  93. codetrail/web/navigation.py +127 -0
  94. codetrail/web/render.py +126 -0
  95. codetrail/web/repository_view.py +230 -0
  96. codetrail/web/security.py +171 -0
  97. codetrail/web/static/brand/favicon.svg +1 -0
  98. codetrail/web/static/brand/mark.svg +1 -0
  99. codetrail/web/static/css/base.css +192 -0
  100. codetrail/web/static/css/components.css +2254 -0
  101. codetrail/web/static/css/content.css +310 -0
  102. codetrail/web/static/css/layout.css +410 -0
  103. codetrail/web/static/css/print.css +42 -0
  104. codetrail/web/static/css/tokens.css +228 -0
  105. codetrail/web/static/js/api.js +49 -0
  106. codetrail/web/static/js/ask.js +182 -0
  107. codetrail/web/static/js/d2-worker.js +34 -0
  108. codetrail/web/static/js/d2.js +28 -0
  109. codetrail/web/static/js/diagram.js +52 -0
  110. codetrail/web/static/js/documentation.js +24 -0
  111. codetrail/web/static/js/learning.js +47 -0
  112. codetrail/web/static/js/main.js +38 -0
  113. codetrail/web/static/js/outline.js +44 -0
  114. codetrail/web/static/js/palette.js +211 -0
  115. codetrail/web/static/js/repository.js +528 -0
  116. codetrail/web/static/js/resize.js +126 -0
  117. codetrail/web/static/js/shortcuts.js +87 -0
  118. codetrail/web/static/js/sidebar.js +194 -0
  119. codetrail/web/static/js/theme-init.js +27 -0
  120. codetrail/web/static/js/theme.js +42 -0
  121. codetrail/web/static/js/update.js +107 -0
  122. codetrail/web/static/vendor/d2/LICENSE +375 -0
  123. codetrail/web/static/vendor/d2/LICENSE-elk +263 -0
  124. codetrail/web/static/vendor/d2/LICENSE-wasm_exec +27 -0
  125. codetrail/web/static/vendor/d2/VERSION +12 -0
  126. codetrail/web/static/vendor/d2/d2.wasm +0 -0
  127. codetrail/web/static/vendor/d2/elk.js +105914 -0
  128. codetrail/web/static/vendor/d2/wasm_exec.js +656 -0
  129. codetrail/web/static/vendor/inter/InterVariable-Italic.woff2 +0 -0
  130. codetrail/web/static/vendor/inter/InterVariable.woff2 +0 -0
  131. codetrail/web/static/vendor/inter/LICENSE.txt +92 -0
  132. codetrail/web/static/vendor/inter/VERSION +6 -0
  133. codetrail/web/static/vendor/noto-sans-arabic/NotoSansArabic-wght.woff2 +0 -0
  134. codetrail/web/static/vendor/noto-sans-arabic/OFL.txt +93 -0
  135. codetrail/web/static/vendor/noto-sans-arabic/VERSION +12 -0
  136. codetrail/web/target_view.py +63 -0
  137. codetrail/web/templates/answers.html +25 -0
  138. codetrail/web/templates/area.html +46 -0
  139. codetrail/web/templates/base.html +37 -0
  140. codetrail/web/templates/decisions.html +30 -0
  141. codetrail/web/templates/diagram.html +10 -0
  142. codetrail/web/templates/digests.html +24 -0
  143. codetrail/web/templates/documentation.html +247 -0
  144. codetrail/web/templates/error.html +9 -0
  145. codetrail/web/templates/estimate.html +48 -0
  146. codetrail/web/templates/fact.html +29 -0
  147. codetrail/web/templates/home.html +196 -0
  148. codetrail/web/templates/layout/ask_panel.html +27 -0
  149. codetrail/web/templates/layout/header.html +32 -0
  150. codetrail/web/templates/layout/icons.html +27 -0
  151. codetrail/web/templates/layout/outline.html +4 -0
  152. codetrail/web/templates/layout/palette.html +27 -0
  153. codetrail/web/templates/layout/shortcuts.html +27 -0
  154. codetrail/web/templates/layout/sidebar.html +79 -0
  155. codetrail/web/templates/layout/update_dialog.html +18 -0
  156. codetrail/web/templates/macros.html +13 -0
  157. codetrail/web/templates/page.html +93 -0
  158. codetrail/web/templates/progress.html +46 -0
  159. codetrail/web/templates/repository.html +369 -0
  160. codetrail/web/templates/search.html +31 -0
  161. codetrail/web/templates/source.html +15 -0
  162. codetrail/web/templates/system.html +43 -0
  163. codetrail/web/templates/system_arrows.html +17 -0
  164. codetrail/web/templates/system_legend.html +9 -0
  165. codetrail/web/templates/update_log.html +30 -0
  166. codetrail/web/trend.py +22 -0
  167. codetrail_cli-1.4.1.dist-info/METADATA +372 -0
  168. codetrail_cli-1.4.1.dist-info/RECORD +171 -0
  169. codetrail_cli-1.4.1.dist-info/WHEEL +4 -0
  170. codetrail_cli-1.4.1.dist-info/entry_points.txt +3 -0
  171. codetrail_cli-1.4.1.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,247 @@
1
+ {% extends "base.html" %}
2
+ {% from "layout/icons.html" import icon %}
3
+ {% block title %}{{ _("Documentation") }}{% endblock %}
4
+ {% block content %}
5
+ {# A list of `listed` items and its folded rest; with a filter, each item carries its kind and the rest's summary
6
+ is written once per kind, so the chips can show the right count without the script translating anything. #}
7
+ {% macro listing(items, filter=none) -%}
8
+ <ul class="list" lang="en" dir="ltr">{% for item in items[:listed] %}<li{% if filter %} data-kind="{{ filter.kinds[loop.index0] }}"{% endif %}>{{ caller(item) }}</li>{% endfor %}</ul>
9
+ {% if items|length > listed %}
10
+ <details class="more">
11
+ <summary>{% if filter %}{% for kind, count in filter.folded.items() %}<span data-more="{{ kind }}" data-count="{{ count }}"{% if kind != "all" %} hidden{% endif %}>{% trans count=count %}{{ count }} more{% endtrans %}</span>{% endfor %}{% else %}{% trans count=items|length - listed %}{{ count }} more{% endtrans %}{% endif %}</summary>
12
+ <ul class="list" lang="en" dir="ltr">{% for item in items[listed:] %}<li{% if filter %} data-kind="{{ filter.kinds[listed + loop.index0] }}"{% endif %}>{{ caller(item) }}</li>{% endfor %}</ul>
13
+ </details>
14
+ {% endif %}
15
+ {%- endmacro %}
16
+ {# Chips only when the list holds two kinds or more: with one, a chip would show what All shows. #}
17
+ {% macro chips(filter, names) -%}
18
+ {% if filter.totals|length > 2 %}
19
+ <div class="segmented filter-chips" role="group" aria-label="{{ _('Show') }}" data-filter hidden>
20
+ <button type="button" data-show="all" aria-pressed="true">{{ _("All") }} · {{ filter.totals["all"]|number }}</button>
21
+ {% for kind, label in names %}{% if filter.totals.get(kind) %}<button type="button" data-show="{{ kind }}" aria-pressed="false">{{ label }} · {{ filter.totals[kind]|number }}</button>{% endif %}{% endfor %}
22
+ </div>
23
+ <p class="visually-hidden" data-filter-status aria-live="polite"></p>
24
+ {% endif %}
25
+ {%- endmacro %}
26
+ {# A stacked bar with its legend; parts are (tone, count, name), and the bar's text alternative names each count. #}
27
+ {% macro stack(parts) -%}
28
+ <div class="stack-bar" dir="ltr" role="img" aria-label="{% for tone, count, name in parts %}{{ name }}: {{ count }}{% if not loop.last %}, {% endif %}{% endfor %}"><svg viewBox="0 0 1000 20" preserveAspectRatio="none" aria-hidden="true">{% for tone, x, width in segments(parts) %}<rect class="{{ tone }}" x="{{ x }}" y="0" height="20" width="{{ [width - 2, 0]|max|round(1) }}"/>{% endfor %}</svg></div>
29
+ <ul class="stack-legend">{% for tone, count, name in parts %}<li><span class="swatch {{ tone }}" aria-hidden="true"></span>{{ name }}<span class="faint">{{ count|number }}</span></li>{% endfor %}</ul>
30
+ {%- endmacro %}
31
+ {# A tile's small bar: the tile already says its numbers in words. #}
32
+ {% macro mini(parts) -%}
33
+ <div class="share-bar" dir="ltr" aria-hidden="true"><svg viewBox="0 0 1000 10" preserveAspectRatio="none">{% for tone, x, width in segments(parts) %}<rect class="{{ tone }}" x="{{ x }}" y="0" height="10" width="{{ width }}"/>{% endfor %}</svg></div>
34
+ {%- endmacro %}
35
+ {% macro hbar(label, count, widest, value, tone="series-fill-1") -%}
36
+ <li><span class="hbar-label">{{ label }}</span><svg class="hbar-track" viewBox="0 0 100 12" preserveAspectRatio="none" aria-hidden="true"><rect class="{{ tone }}" x="0" y="0" height="12" width="{{ (count / widest * 100)|round(1) if widest else 0 }}" rx="2"/></svg><span class="hbar-value">{{ value }}</span></li>
37
+ {%- endmacro %}
38
+ {% macro page_bar(row, widest) -%}
39
+ {% set documented = row.documented / widest * 100 %}
40
+ <li><a class="hbar-label" href="/pages/{{ row.page.id }}" lang="en" dir="ltr">{{ row.page.title }}</a><svg class="hbar-track" viewBox="0 0 100 12" preserveAspectRatio="none" role="img" aria-label="{% trans documented=row.documented, inferred=row.inferred %}{{ documented }} documented, {{ inferred }} inferred{% endtrans %}"><rect class="tone-documented" x="0" y="0" height="12" width="{{ documented|round(1) }}"/><rect class="tone-inferred" x="{{ documented|round(1) }}" y="0" height="12" width="{{ (row.inferred / widest * 100)|round(1) }}"/></svg><span class="hbar-value">{{ row.documented }} / {{ row.inferred }}</span></li>
41
+ {%- endmacro %}
42
+ {% macro fact_link(entity) -%}
43
+ <a class="grow" href="/facts/{{ entity.id|urlencode|replace("/", "%2F") }}">{{ entity.id }}</a>
44
+ {%- endmacro %}
45
+ {% macro decision_link(decision) -%}
46
+ <a class="grow" href="/facts/{{ decision.id|urlencode|replace("/", "%2F") }}">{{ decision.attributes.get("number", "") }} · {{ decision.attributes.get("title", "") }}</a>{% if decision.attributes.get("date") %}<span class="faint">{{ decision.attributes.date }}</span>{% endif %}
47
+ {%- endmacro %}
48
+ {% macro share(value) -%}
49
+ {% if value.denominator %}{% trans numerator=value.numerator, denominator=value.denominator %}{{ numerator }} of {{ denominator }}{% endtrans %}{% else %}{{ _("nothing to measure yet") }}{% endif %}
50
+ {%- endmacro %}
51
+ {% macro about(text) -%}
52
+ <details class="doc-about"><summary>{{ _("What this counts") }}</summary><p class="muted">{{ text }}</p></details>
53
+ {%- endmacro %}
54
+ {% macro tile_card(tile, label, title_id, parts=none) %}
55
+ <section class="stat metric" aria-labelledby="{{ title_id }}">
56
+ <span class="card-label" id="{{ title_id }}">{{ label }}</span>
57
+ {% if tile.value is none %}
58
+ <span class="stat-value">—</span><span class="stat-note">{{ _("Not available just now.") }}</span>
59
+ {% elif tile.metric == "adr_attention" %}
60
+ <span class="stat-value{% if tile.value.numerator %} behind{% endif %}">{{ tile.value.numerator }}</span>
61
+ <span class="stat-note">{{ _("proposed too long, or retired and still cited") }}</span>
62
+ {% elif tile.metric == "documented_share" and not tile.value.denominator %}
63
+ <span class="stat-value">—</span><span class="stat-note">{{ _("No pages yet: the guide's rationale is measured once your assistant writes it.") }}</span>
64
+ <button type="button" class="button small" data-update-button>{{ _("Update the guide") }}</button>
65
+ {% else %}
66
+ <span class="stat-value">{% if tile.percent is not none %}{{ tile.percent }}%{% else %}—{% endif %}</span>
67
+ <span class="stat-note">{{ share(tile.value) }}</span>
68
+ {% if parts %}{{ mini(parts) }}{% endif %}
69
+ {% endif %}
70
+ {% if tile.value is not none and (tile.value.denominator or tile.metric == "adr_attention") %}
71
+ <span class="stat-note">{% if tile.change is none %}{{ _("No earlier update to compare with.") }}{% elif tile.metric == "adr_attention" %}{% trans change="%+d"|format(tile.change) %}{{ change }} since the last update{% endtrans %}{% else %}{% trans change="%+d"|format(tile.change) %}{{ change }} points since the last update{% endtrans %}{% endif %}</span>
72
+ {% endif %}
73
+ {% if tile.points %}
74
+ {% set count = tile.trend_text.split(", ")|length %}
75
+ <svg class="trend" viewBox="0 0 120 32" role="img" aria-label="{% trans count=count, values=tile.trend_text %}Trend over {{ count }} update: {{ values }}{% pluralize %}Trend over {{ count }} updates: {{ values }}{% endtrans %}"><polyline points="{{ tile.points }}"/></svg>
76
+ {% endif %}
77
+ </section>
78
+ {% endmacro %}
79
+ <div class="wide">
80
+ <header class="page-head">
81
+ <h1>{{ _("Documentation") }}</h1>
82
+ <p class="muted">{{ _("How well the repository explains itself: its guide's rationale, its commits, its decisions and its documents. Measured from the code and the guide, without calling your assistant.") }}</p>
83
+ </header>
84
+ {% if report is none %}
85
+ <section class="empty">
86
+ {% if unavailable %}
87
+ <h2>{{ _("The metrics aren't available just now") }}</h2>
88
+ <p>{{ _("Codetrail couldn't read the target's data; the log says why.") }}</p>
89
+ {% else %}
90
+ <h2>{{ _("No facts yet") }}</h2>
91
+ <p>{{ _("Update the guide once: reading the code is free, and the metrics come from it.") }}</p>
92
+ <button type="button" class="button primary" data-update-button>{{ _("Update the guide") }}</button>
93
+ {% endif %}
94
+ </section>
95
+ {% else %}
96
+ {% set rationale, commits, decisions = report.rationale, report.commits, report.decisions %}
97
+ {% set mentions, explained, has_pages = report.mentions, report.explained, report.rationale.pages %}
98
+ <div class="doc-layout">
99
+ <aside class="doc-overview" aria-label="{{ _('Overview') }}" tabindex="0">{# focusable: it may scroll on its own #}
100
+ {{ tile_card(tiles["documented_share"], _("Documented rationale"), "tile-documented",
101
+ [("tone-documented", rationale.documented, ""), ("tone-inferred", rationale.inferred, "")]) }}
102
+ {{ tile_card(tiles["commit_why_share"], _("Commits that explain why"), "tile-commits",
103
+ [("series-fill-1", commits.explains_why, ""), ("", commits.measured - commits.explains_why, "")] if commits else none) }}
104
+ {{ tile_card(tiles["adr_attention"], _("ADRs needing attention"), "tile-adrs") }}
105
+ {{ tile_card(tiles["mentioned_share"], _("Mentioned in a document"), "tile-mentioned",
106
+ [("series-fill-1", mentions.mentioned|length, ""), ("", mentions.unmentioned|length, "")]) }}
107
+ </aside>
108
+ <div class="doc-sections">
109
+
110
+ <section id="rationale" aria-labelledby="rationale-title">
111
+ <details class="doc-fold"{% if open_section == "rationale" %} open{% endif %}>
112
+ <summary><h2 id="rationale-title">{{ icon("chevron") }}{{ _("Documented rationale") }}</h2><span class="doc-todo">{% if not has_pages %}{{ _("No pages yet") }}{% elif rationale.inferred %}{% trans count=rationale.inferred %}{{ count }} inferred, worth documenting{% pluralize %}{{ count }} inferred, worth documenting{% endtrans %}{% else %}{{ _("Nothing inferred") }}{% endif %}</span></summary>
113
+ <div class="doc-body">
114
+ {{ about(_("Rationale blocks in the guide's area and concept pages: documented ones quote a source Codetrail checked, inferred ones are the assistant's reading. It counts only what the guide wrote about.")) }}
115
+ {% if not has_pages %}
116
+ <p class="muted">{{ _("No pages yet: the guide's rationale is measured once your assistant writes it.") }}</p>
117
+ {% else %}
118
+ {{ stack([("heat-4", rationale.by_source.adr, _("ADRs")), ("heat-3", rationale.by_source.docs, _("Documents")),
119
+ ("heat-2", rationale.by_source.code, _("Code comments")), ("heat-1", rationale.by_source.commit, _("Commit messages")),
120
+ ("tone-inferred", rationale.inferred, _("Inferred"))]) }}
121
+ <h3>{{ _("By page, most inferred first") }}</h3>
122
+ {% set ns = namespace(widest=1) %}{% for row in rationale.pages %}{% set ns.widest = [ns.widest, row.documented + row.inferred]|max %}{% endfor %}
123
+ <ul class="hbars page-bars" dir="ltr">{% for row in rationale.pages[:listed] %}{{ page_bar(row, ns.widest) }}{% endfor %}</ul>
124
+ {% if rationale.pages|length > listed %}
125
+ <details class="more"><summary>{% trans count=rationale.pages|length - listed %}{{ count }} more{% endtrans %}</summary><ul class="hbars page-bars" dir="ltr">{% for row in rationale.pages[listed:] %}{{ page_bar(row, ns.widest) }}{% endfor %}</ul></details>
126
+ {% endif %}
127
+ {% if rationale.inferred_blocks %}
128
+ <h3>{{ _("Inferred: worth documenting") }}</h3>
129
+ {% call(block) listing(rationale.inferred_blocks) %}<a class="grow" href="/pages/{{ block.page.id }}">{{ block.first_line }}</a><span class="faint">{{ block.page.title }}</span>{% endcall %}
130
+ {% endif %}
131
+ {% endif %}
132
+ </div>
133
+ </details>
134
+ </section>
135
+
136
+ <section id="commits" aria-labelledby="commits-title">
137
+ <details class="doc-fold"{% if open_section == "commits" %} open{% endif %}>
138
+ <summary><h2 id="commits-title">{{ icon("chevron") }}{{ _("Commits that explain why") }}</h2><span class="doc-todo">{% if commits is none %}{{ _("Not available just now.") }}{% else %}{% trans count=commits.without_why|length, measured=commits.measured %}{{ count }} without a why, of the latest {{ measured }}{% pluralize %}{{ count }} without a why, of the latest {{ measured }}{% endtrans %}{% endif %}</span></summary>
139
+ <div class="doc-body">
140
+ {% if commits is none %}
141
+ <p class="muted">{{ _("The commit history couldn't be read just now; the log says why.") }}</p>
142
+ {% else %}
143
+ {{ about(_("The latest commits on the branch, merges left out. A commit explains why when its body matches the target's commit_why_pattern.")) }}
144
+ {{ stack([("tone-documented", commits.explains_why, _("Explain why")), ("tone-other", commits.body_only, _("Body without a why")),
145
+ ("tone-quiet", commits.subject_only, _("Subject only"))]) }}
146
+ {% if commits.withheld %}<p class="muted">{% trans count=commits.withheld %}{{ count }} message withheld: gitleaks flagged it{% pluralize %}{{ count }} messages withheld: gitleaks flagged them{% endtrans %}</p>{% endif %}
147
+ {% if commits.without_why %}
148
+ <h3>{{ _("Without a why") }}</h3>
149
+ <div data-filterable>
150
+ {{ chips(commit_filter, [("body-only", _("Body without a why")), ("subject-only", _("Subject only"))]) }}
151
+ {% call(commit) listing(commits.without_why, commit_filter) %}<code>{{ commit.sha[:7] }}</code><span class="grow">{{ commit.subject }}</span><span class="faint">{% if commit.body %}{{ _("body without a why") }}{% else %}{{ _("subject only") }}{% endif %}</span>{% endcall %}
152
+ </div>
153
+ {% endif %}
154
+ {% endif %}
155
+ </div>
156
+ </details>
157
+ </section>
158
+
159
+ <section id="decisions" aria-labelledby="decisions-title">
160
+ {% set to_check = decisions.undated|length + (decisions.uncited|length if has_pages else 0) %}
161
+ <details class="doc-fold"{% if open_section == "decisions" %} open{% endif %}>
162
+ <summary><h2 id="decisions-title">{{ icon("chevron") }}{{ _("Architecture decisions") }}</h2><span class="doc-todo">{% if not decisions.by_status %}{{ _("None found") }}{% else %}{% trans count=decisions.attention %}{{ count }} needs attention{% pluralize %}{{ count }} need attention{% endtrans %}{% if to_check %} · {% trans count=to_check %}{{ count }} to check{% pluralize %}{{ count }} to check{% endtrans %}{% endif %}{% endif %}</span></summary>
163
+ <div class="doc-body">
164
+ {% if not decisions.by_status %}
165
+ <p class="muted">{{ _("No architecture decision records were found where the target's settings say they live.") }}</p>
166
+ {% else %}
167
+ {{ about(_("A proposed ADR needs attention once it waits longer than the target's settings allow, and a superseded or deprecated one while a guide page still cites it. Worth a look: proposed ADRs with no date, and accepted ones no page cites.")) }}
168
+ <div class="doc-columns">
169
+ <div>
170
+ <h3>{{ _("By status") }}</h3>
171
+ {% set widest = decisions.by_status.values()|map("length")|max %}
172
+ <ul class="hbars" lang="en" dir="ltr">{% for status, found in decisions.by_status|dictsort %}{{ hbar(status, found|length, widest, found|length|number, "series-fill-5" if status in ("superseded", "deprecated") else "series-fill-1") }}{% endfor %}</ul>
173
+ </div>
174
+ <div>
175
+ {% if decisions.overdue or decisions.superseded_cited %}
176
+ <h3>{{ _("Needs attention") }}</h3>
177
+ {% if decisions.overdue %}
178
+ <h4>{{ _("Proposed for too long") }}</h4>
179
+ {% call(decision) listing(decisions.overdue) %}{{ decision_link(decision) }}<span class="faint">{% trans count=days_proposed(decision, today) %}proposed {{ count }} day ago{% pluralize %}proposed {{ count }} days ago{% endtrans %}</span>{% endcall %}
180
+ {% endif %}
181
+ {% if decisions.superseded_cited %}
182
+ <h4>{{ _("Superseded or deprecated, still cited by the guide") }}</h4>
183
+ {% call(decision) listing(decisions.superseded_cited) %}{{ decision_link(decision) }}{% endcall %}
184
+ {% endif %}
185
+ {% endif %}
186
+ {% if to_check %}
187
+ <h3>{{ _("Worth a look") }}</h3>
188
+ {% if decisions.undated %}
189
+ <h4>{{ _("Proposed, with no date") }}</h4>
190
+ {% call(decision) listing(decisions.undated) %}{{ decision_link(decision) }}{% endcall %}
191
+ {% endif %}
192
+ {% if decisions.uncited and has_pages %}
193
+ <h4>{{ _("Accepted, but no page cites them") }}</h4>
194
+ {% call(decision) listing(decisions.uncited) %}{{ decision_link(decision) }}{% endcall %}
195
+ {% endif %}
196
+ {% endif %}
197
+ </div>
198
+ </div>
199
+ {% endif %}
200
+ </div>
201
+ </details>
202
+ </section>
203
+
204
+ <section id="coverage" aria-labelledby="coverage-title">
205
+ {% set mention_total = mentions.mentioned|length + mentions.unmentioned|length %}
206
+ <details class="doc-fold"{% if open_section == "coverage" %} open{% endif %}>
207
+ <summary><h2 id="coverage-title">{{ icon("chevron") }}{{ _("Mentioned and explained") }}</h2><span class="doc-todo">{% trans count=mentions.unmentioned|length %}{{ count }} never mentioned{% pluralize %}{{ count }} never mentioned{% endtrans %}{% if has_pages %} · {% trans count=explained.unexplained|length %}{{ count }} unexplained{% pluralize %}{{ count }} unexplained{% endtrans %}{% endif %}</span></summary>
208
+ <div class="doc-body">
209
+ {{ about(_("A package counts when a document names it as a whole word, a project when its folder holds a README or a document names its folder, and a resource when a document names its address. A fact is explained when it falls in the scope of a written area or concept page.")) }}
210
+ <p class="muted">{{ _("External packages, projects and infrastructure resources that some document names. A mention isn't a reason: this only shows what no document talks about at all.") }}</p>
211
+ <ul class="hbars coverage-bars" dir="ltr">
212
+ {% set value %}{% trans numerator=mentions.mentioned|length, denominator=mention_total %}{{ numerator }} of {{ denominator }}{% endtrans %}{% endset %}
213
+ {{ hbar(_("Mentioned in a document"), mentions.mentioned|length, mention_total, value) }}
214
+ {% if has_pages %}
215
+ {% set value %}{% trans numerator=explained.explained, denominator=explained.total %}{{ numerator }} of {{ denominator }}{% endtrans %}{% endset %}
216
+ {{ hbar(_("Explained by a guide page"), explained.explained, explained.total, value) }}
217
+ {% endif %}
218
+ </ul>
219
+ {% if not has_pages %}
220
+ <p class="muted">{{ _("Which facts the guide explains, and which ADRs it cites, are measured once your assistant has written its pages.") }}</p>
221
+ {% endif %}
222
+ <div class="doc-columns">
223
+ {% if mentions.unmentioned %}
224
+ <div>
225
+ <h3>{{ _("Not mentioned in any document") }}</h3>
226
+ <div data-filterable>
227
+ {{ chips(fact_filter, [("package", _("Packages")), ("project", _("Projects")), ("resource", _("Resources"))]) }}
228
+ {% call(entity) listing(mentions.unmentioned, fact_filter) %}{{ fact_link(entity) }}{% endcall %}
229
+ </div>
230
+ </div>
231
+ {% endif %}
232
+ {% if explained.unexplained and has_pages %}
233
+ <div>
234
+ <h3>{{ _("Not explained by any page") }}</h3>
235
+ {% call(entity) listing(explained.unexplained) %}{{ fact_link(entity) }}{% endcall %}
236
+ </div>
237
+ {% endif %}
238
+ </div>
239
+ </div>
240
+ </details>
241
+ </section>
242
+
243
+ </div>
244
+ </div>
245
+ {% endif %}
246
+ </div>
247
+ {% endblock %}
@@ -0,0 +1,9 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}{{ _("Not found") }}{% endblock %}
3
+ {% block content %}
4
+ <section class="empty">
5
+ <h1>{{ _("Not found") }}</h1>
6
+ <p>{{ _("There's nothing here. It may be excluded, or not in the guide yet.") }}</p>
7
+ <p><a class="button" href="/">{{ _("Back to the start") }}</a></p>
8
+ </section>
9
+ {% endblock %}
@@ -0,0 +1,48 @@
1
+ <h2 id="estimate-title">{{ _("Before the update") }}</h2>
2
+ <p>{{ _("The facts are refreshed. Writing the guide will call:") }}</p>
3
+ <table class="estimate">
4
+ <thead>
5
+ <tr><th>{{ _("Call") }}</th><th>{{ _("Provider and model") }}</th><th>{{ _("How many") }}</th><th>{{ _("Tokens each") }}</th><th>{{ _("Cost each") }}</th></tr>
6
+ </thead>
7
+ <tbody>
8
+ {% for line in estimate.lines %}
9
+ <tr>
10
+ <td>{{ line.kind }}</td>
11
+ <td>{{ line.provider }} · {{ line.model or _("default model") }}</td>
12
+ <td>{% if line.expected == line.maximum %}{{ line.expected }}{% else %}{% trans expected=line.expected, maximum=line.maximum %}{{ expected }} to {{ maximum }}{% endtrans %}{% endif %}</td>
13
+ <td><bdi dir="ltr">~{{ line.tokens_each | tokens }}</bdi>{% if not line.from_history %} <span class="muted">({{ _("a starting guess") }})</span>{% endif %}</td>
14
+ <td>{% if line.cost_each_usd is none %}{{ _("no price configured") }}{% elif line.cost_each_usd == 0 %}{{ _("free") }}{% else %}<bdi dir="ltr">~{{ line.cost_each_usd | dollars }}</bdi>{% endif %}</td>
15
+ </tr>
16
+ {% endfor %}
17
+ </tbody>
18
+ </table>
19
+ <p>
20
+ {% trans expected=estimate.expected_tokens|tokens, maximum=estimate.maximum_tokens|tokens %}Expected: ~{{ expected }} tokens; at most ~{{ maximum }}.{% endtrans %}
21
+ {% if estimate.expected_usd is not none %}{% trans expected=estimate.expected_usd|dollars, maximum=estimate.maximum_usd|dollars %}At API prices: ~{{ expected }}, at most {{ maximum }}.{% endtrans %}{% endif %}
22
+ {% trans budget=estimate.budget_usd|dollars, tokens=estimate.budget_tokens|tokens %}The update stops at its budget of {{ budget }} or {{ tokens }} tokens, whichever comes first.{% endtrans %}
23
+ </p>
24
+ {% macro why(page) -%}
25
+ {%- if page.reason == "update" %}{% trans count=page.changed %}one of its facts changed in this update{% pluralize %}{{ count }} of its facts changed in this update{% endtrans %}
26
+ {%- elif page.reason == "new" %}{{ _("a new page") }}
27
+ {%- elif page.reason == "outline" %}{{ _("its outline entry changed") }}
28
+ {%- else %}{{ _("catching up, since its facts changed after it was written") }}{% endif -%}
29
+ {%- endmacro %}
30
+ {% if estimate.pages %}
31
+ <p>{{ _("Pages, in the order they’re written:") }}</p>
32
+ <ol class="estimate-pages">
33
+ {% for page in estimate.pages %}
34
+ <li>“{{ page.title }}”{% if page.revise %} ({{ _("revised") }}){% endif %}: {{ why(page) }}</li>
35
+ {% endfor %}
36
+ </ol>
37
+ {% endif %}
38
+ {% if estimate.skipped %}
39
+ <p>{{ _("Skipped, since they failed last time and nothing in them changed:") }} {{ estimate.skipped|join(", ") }}</p>
40
+ {% endif %}
41
+ <ul class="estimate-notes">
42
+ {% for provider, method in estimate.sign_ins.items() %}
43
+ <li>{{ provider }}: {{ method }}{% if "subscription" in method|lower %}: {{ _("no charge; the work counts against your plan's usage limits.") }}{% elif provider != "local" %}: {{ _("billed to that account.") }}{% endif %}</li>
44
+ {% endfor %}
45
+ {% for reading in estimate.plan_usage %}
46
+ <li>{% trans provider=reading.provider, window=reading.window|replace("_", " "), used=(reading.utilization * 100)|round|int, when=reading.observed_at|utc %}Plan usage ({{ provider }}, {{ window }}): {{ used }}%, as of {{ when }}.{% endtrans %}</li>
47
+ {% endfor %}
48
+ </ul>
@@ -0,0 +1,29 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}{{ entity.id }}{% endblock %}
3
+ {% block content %}
4
+ <div class="wide">
5
+ <header class="page-head">
6
+ <p class="eyebrow">{{ _("Fact") }} · <span lang="en" dir="ltr">{{ entity.kind }}</span></p>
7
+ <h1 lang="en" dir="ltr">{{ entity.id }}</h1>
8
+ </header>
9
+ {% if entity.attributes %}
10
+ <table class="attributes" lang="en" dir="ltr">
11
+ {% for key, value in entity.attributes.items() %}<tr><th>{{ key }}</th><td>{{ value }}</td></tr>{% endfor %}
12
+ </table>
13
+ {% endif %}
14
+ <h2>{{ _("Found in") }}</h2>
15
+ <ul class="list" lang="en" dir="ltr">
16
+ {% for source in entity.sources %}
17
+ <li><a class="grow" href="/source/{{ source.path|urlencode }}{% if source.start_line %}#L{{ source.start_line }}{% endif %}">{{ source.path }}{% if source.start_line %}:{{ source.start_line }}{% endif %}</a></li>
18
+ {% endfor %}
19
+ </ul>
20
+ {% if outgoing %}
21
+ <h2>{{ _("Points to") }}</h2>
22
+ <ul class="list" lang="en" dir="ltr">{% for relation in outgoing %}<li><span class="kind">{{ relation.kind }}</span><a class="grow" href="/facts/{{ relation.target_id|urlencode|replace("/", "%2F") }}">{{ relation.target_id }}</a></li>{% endfor %}</ul>
23
+ {% endif %}
24
+ {% if incoming %}
25
+ <h2>{{ _("Pointed to by") }}</h2>
26
+ <ul class="list" lang="en" dir="ltr">{% for relation in incoming %}<li><a class="grow" href="/facts/{{ relation.source_id|urlencode|replace("/", "%2F") }}">{{ relation.source_id }}</a><span class="kind">{{ relation.kind }}</span></li>{% endfor %}</ul>
27
+ {% endif %}
28
+ </div>
29
+ {% endblock %}
@@ -0,0 +1,196 @@
1
+ {% extends "base.html" %}
2
+ {% from "layout/icons.html" import icon %}
3
+ {% block content %}
4
+ {% macro since_last_update() %}
5
+ <section class="card rail-card" aria-labelledby="update-title">
6
+ <p class="card-label" id="update-title">{{ _("Since the last update") }}</p>
7
+ <div class="rail-row">
8
+ {% if signal is none %}
9
+ <span class="chip">—</span>
10
+ {% elif signal.updating %}
11
+ <span class="chip">…</span>
12
+ {% elif signal.commits == 0 %}
13
+ <span class="chip chip-ok">{{ _("Up to date") }}</span>
14
+ {% else %}
15
+ <span class="chip chip-behind">{% trans count=signal.commits %}{{ count }} commit behind{% pluralize %}{{ count }} commits behind{% endtrans %}</span>
16
+ {% endif %}
17
+ <button type="button" class="button small" data-update-button>{{ _("Update the guide") }}</button>
18
+ </div>
19
+ <p class="stat-note">
20
+ {%- if signal is none %}{{ _("Couldn't check for new commits just now.") }}
21
+ {%- elif signal.updating %}{{ _("An update is running.") }}
22
+ {%- elif signal.commits == 0 %}{{ _("The guide matches the branch.") }}
23
+ {%- else %}{% trans count=signal.merges %}{{ count }} merge landed{% pluralize %}{{ count }} merges landed{% endtrans %}{% if signal.areas %} · <span lang="en" dir="ltr">{{ signal.areas[:3] | join(", ") }}</span>{% endif %}{% endif -%}
24
+ </p>
25
+ {% if last_update %}<p class="stat-note">{% trans tokens=last_update.tokens|tokens, cost=last_update.cost_usd|dollars %}The last update used ~{{ tokens }} tokens ({{ cost }} at API prices).{% endtrans %}</p>{% endif %}
26
+ </section>
27
+ {% endmacro %}
28
+ {% macro what_changed() %}
29
+ <section class="card rail-card" aria-labelledby="changed-title">
30
+ <p class="card-label" id="changed-title">{{ _("What changed") }}</p>
31
+ {% if unread_digests %}
32
+ <a class="stat-link" href="/pages/{{ unread_digests[0].id }}" lang="en" dir="ltr">{{ unread_digests[0].title }}</a>
33
+ <p class="stat-note">{% trans count=unread_digests|length %}{{ count }} unread digest{% pluralize %}{{ count }} unread digests{% endtrans %}</p>
34
+ {% elif latest_digest %}
35
+ <a class="stat-link" href="/pages/{{ latest_digest.id }}" lang="en" dir="ltr">{{ latest_digest.title }}</a>
36
+ <p class="stat-note">{{ _("You're caught up.") }}</p>
37
+ {% else %}
38
+ <p class="stat-note">{{ _("No digest yet.") }}</p>
39
+ {% endif %}
40
+ </section>
41
+ {% endmacro %}
42
+ {% macro system_card() %}
43
+ {% if system %}
44
+ <section class="card rail-card metric-card" data-hue="system" aria-labelledby="system-title">
45
+ <span class="hue">{{ icon("system") }}</span>
46
+ <div>
47
+ <p class="card-label" id="system-title">{{ _("Your system") }}</p>
48
+ <p class="metric-figure">{% trans count=system.parts %}{{ count }} part{% pluralize %}{{ count }} parts{% endtrans %} · {% trans count=system.connections %}{{ count }} connection{% pluralize %}{{ count }} connections{% endtrans %}{{ _(", drawn from the code") }}</p>
49
+ <a class="stat-note" href="/system">{{ _("See how it fits together") }}</a>
50
+ </div>
51
+ </section>
52
+ {% endif %}
53
+ {% endmacro %}
54
+ {% macro documentation_card() %}
55
+ {% if snapshot %}
56
+ <section class="card rail-card metric-card" data-hue="documented" aria-labelledby="documentation-title">
57
+ <span class="hue">{{ icon("metrics") }}</span>
58
+ <div class="grow">
59
+ <p class="card-label" id="documentation-title">{{ _("Documentation") }}</p>
60
+ {% if documentation and documentation.percent is not none %}
61
+ <p class="metric-figure">{% trans percent=documentation.percent %}{{ percent }}% of the guide's rationale is documented{% endtrans %}</p>
62
+ <progress class="bar" max="100" value="{{ documentation.percent }}" aria-hidden="true"></progress>
63
+ {% if documentation.change is not none %}<p class="stat-note">{% trans change="%+d"|format(documentation.change) %}{{ change }} points since the last update{% endtrans %}</p>{% endif %}
64
+ {% else %}
65
+ <p class="stat-note">{{ _("How well the repository explains itself: rationale, commits, decisions and documents.") }}</p>
66
+ {% endif %}
67
+ <a class="stat-note" href="/documentation">{{ _("See what's documented") }}</a>
68
+ </div>
69
+ </section>
70
+ {% endif %}
71
+ {% endmacro %}
72
+ {% macro repository_card() %}
73
+ {% if snapshot and repository %}
74
+ <section class="card rail-card metric-card" data-hue="repository" aria-labelledby="repository-title">
75
+ <span class="hue">{{ icon("repository") }}</span>
76
+ <div>
77
+ <p class="card-label" id="repository-title">{{ _("Repository") }}</p>
78
+ {% if repository.commits is not none %}<p class="metric-figure">{% trans count=repository.commits, commits=repository.commits|number %}{{ commits }} commit{% pluralize %}{{ commits }} commits{% endtrans %}</p>{% endif %}
79
+ <p class="stat-note">{% if repository.first %}{% trans date=repository.first.isoformat() %}Started on {{ date }}{% endtrans %}{% if repository.code_lines is not none %} · {% endif %}{% endif %}{% if repository.code_lines is not none %}{% trans count=repository.code_lines, lines=repository.code_lines|number %}{{ lines }} line of code{% pluralize %}{{ lines }} lines of code{% endtrans %}{% endif %}</p>
80
+ <a class="stat-note" href="/repository">{{ _("See the repository's statistics") }}</a>
81
+ </div>
82
+ </section>
83
+ {% endif %}
84
+ {% endmacro %}
85
+ {% macro browse_the_code() %}
86
+ <section class="card rail-card" aria-labelledby="browse-title">
87
+ <p class="card-label" id="browse-title">{{ _("Browse the code") }}</p>
88
+ {% if areas %}
89
+ <ul class="folder-chips" lang="en" dir="ltr">
90
+ {% for area, files in areas %}
91
+ <li><a class="folder-chip" href="/areas/{{ area|urlencode }}">{{ icon("folder") }} {{ area }}/ <span class="faint">{{ files }}</span></a></li>
92
+ {% endfor %}
93
+ </ul>
94
+ {% else %}
95
+ <p class="stat-note">{% trans name=target %}No sources yet. Run <code>codetrail update {{ name }}</code> to read the repository.{% endtrans %}</p>
96
+ {% endif %}
97
+ {% if counts %}
98
+ <p class="stat-note">{{ _("Facts:") }} {% for kind, count in counts %}<span lang="en" dir="ltr">{{ kind }}</span> {{ count }}{% if not loop.last %} · {% endif %}{% endfor %}</p>
99
+ {% endif %}
100
+ </section>
101
+ {% endmacro %}
102
+ <div class="wide home">
103
+ <header class="home-head">
104
+ <h1 lang="en" dir="ltr">{{ target }}</h1>
105
+ <p class="meta" lang="en" dir="ltr">{% if nav.branch %}{{ nav.branch }}{% endif %}{% if snapshot %} · {{ snapshot.commit[:12] }} · {{ snapshot.taken_at | utc }}{% endif %}</p>
106
+ </header>
107
+
108
+ <div class="home-columns">
109
+ <div class="home-main">
110
+ {% if not guide_pages %}
111
+ <section class="empty">
112
+ <h2>{{ _("Run your first update") }}</h2>
113
+ <p>{{ _("Codetrail reads the code for free, then your assistant writes the guide: concept pages, guided paths and checks. You'll see the estimate before anything is spent.") }}</p>
114
+ <button type="button" class="button primary" data-update-button>{{ _("Update the guide") }}</button>
115
+ <p class="faint">{% trans name=target %}Or from a terminal: <code>codetrail update {{ name }}</code>{% endtrans %}</p>
116
+ </section>
117
+ {% else %}
118
+
119
+ {% if continue_reading %}
120
+ {% set path, step, steps_learned, steps_total = continue_reading %}
121
+ <section class="card card-accent continue" aria-labelledby="continue-title">
122
+ <svg class="ring" viewBox="0 0 44 44" aria-hidden="true" focusable="false"><circle class="ring-track" cx="22" cy="22" r="18" pathLength="100"/>{% if steps_learned %}<circle class="ring-value" cx="22" cy="22" r="18" pathLength="100" stroke-dasharray="{{ (100 * steps_learned / (steps_total or 1)) | round(1) }} 100"/>{% endif %}</svg>
123
+ <div class="grow">
124
+ <p class="card-label" id="continue-title">{{ _("Continue where you left off") }}</p>
125
+ <p class="continue-path" lang="en" dir="ltr">{{ path.title }}</p>
126
+ <p class="card-title" lang="en" dir="ltr">{{ step.title }}</p>
127
+ <p class="faint">{% trans learned=steps_learned, total=steps_total %}{{ learned }} of {{ total }} learned{% endtrans %}</p>
128
+ </div>
129
+ <a class="button primary" href="/pages/{{ step.id }}?path={{ path.id }}">{{ _("Continue") }} {{ icon("arrow") }}</a>
130
+ </section>
131
+ {% elif nav.paths %}
132
+ <section class="card card-accent">
133
+ <p class="card-label">{{ _("Every path learned") }}</p>
134
+ <p class="card-title">{{ _("You've learned every guided path. Updates will tell you what changes.") }}</p>
135
+ </section>
136
+ {% endif %}
137
+
138
+ <section class="card home-paths" aria-labelledby="paths-title">
139
+ <div class="section-head">
140
+ <h2 id="paths-title">{{ _("Paths") }}</h2>
141
+ <a href="/progress">{% trans learned=learned, total=guide_pages|length %}{{ learned }} of {{ total }} learned{% endtrans %}</a>
142
+ </div>
143
+ {% if nav.paths %}
144
+ <ul class="path-rows" lang="en" dir="ltr">
145
+ {% for path, steps_learned, steps_total in nav.paths %}
146
+ <li><a href="/pages/{{ path.id }}">{{ path.title }}</a><progress class="bar" max="{{ steps_total or 1 }}" value="{{ steps_learned }}" aria-hidden="true"></progress><span class="faint">{{ steps_learned }}/{{ steps_total }}</span></li>
147
+ {% endfor %}
148
+ </ul>
149
+ {% endif %}
150
+ </section>
151
+
152
+ {% if stale_pages %}
153
+ <section>
154
+ <h2>{{ _("Changed since you learned them") }}</h2>
155
+ <ul class="list" lang="en" dir="ltr">
156
+ {% for page in stale_pages %}<li><a class="grow" href="/pages/{{ page.id }}">{{ page.title }}</a><span class="pill pill-stale">{{ _("changed") }}</span></li>{% endfor %}
157
+ </ul>
158
+ </section>
159
+ {% endif %}
160
+
161
+ <section aria-labelledby="areas-title">
162
+ <h2 id="areas-title">{{ _("Areas") }}</h2>
163
+ <ul class="grid area-grid" lang="en" dir="ltr">
164
+ {% for area in nav.areas %}
165
+ {% set state = nav.statuses.get(area.id, "unread") %}
166
+ <li><a class="tile area-tile" data-status="{{ state }}" href="/pages/{{ area.id }}"><span class="tile-title">{{ area.title }}</span>
167
+ {% if state == "learned" %}<span class="pill pill-learned">{{ _("Learned") }}</span>{% elif state == "stale" %}<span class="pill pill-stale">{{ _("Changed") }}</span>{% elif state == "read" %}<span class="pill pill-read">{{ _("Read") }}</span>{% else %}<span class="pill">{{ _("Not read yet") }}</span>{% endif %}</a></li>
168
+ {% endfor %}
169
+ </ul>
170
+ </section>
171
+ {% endif %}
172
+ </div>
173
+
174
+ <aside class="home-rail" aria-label="{{ _('The repository') }}">
175
+ {% if snapshot %}{{ since_last_update() }}{% endif %}
176
+ {% if guide_pages %}{{ what_changed() }}{% endif %}
177
+ {{ system_card() }}
178
+ {{ documentation_card() }}
179
+ {{ repository_card() }}
180
+ {% if guide_pages %}
181
+ <section class="card rail-card" aria-labelledby="answers-title">
182
+ <div class="section-head"><p class="card-label" id="answers-title">{{ _("Your saved answers") }}</p>{% if nav.answer_count %}<a class="stat-note" href="/answers">{{ _("All saved answers") }}</a>{% endif %}</div>
183
+ {% if nav.answers %}
184
+ <ul class="rail-list">
185
+ {% for answer in nav.answers %}<li><a href="/pages/{{ answer.id }}" dir="auto">{{ answer.title }}</a></li>{% endfor %}
186
+ </ul>
187
+ {% else %}
188
+ <p class="stat-note">{{ _("Ask a question from any page (press A) and save the answers worth keeping. They'll be here.") }}</p>
189
+ {% endif %}
190
+ </section>
191
+ {% endif %}
192
+ {{ browse_the_code() }}
193
+ </aside>
194
+ </div>
195
+ </div>
196
+ {% endblock %}
@@ -0,0 +1,27 @@
1
+ {% from "macros.html" import cost with context %}
2
+ {% from "layout/icons.html" import icon %}
3
+ <aside class="ask-panel" data-ask {% if page is defined and page.id %}data-page-id="{{ page.id }}"{% endif %}
4
+ data-language="{{ language.code }}"
5
+ data-label-save="{{ _('Save to guide') }}" data-label-saved="{{ _('Saved · open it') }}"
6
+ data-label-save-failed="{{ _('Couldn’t save the answer.') }}"
7
+ aria-labelledby="ask-title" id="ask-panel" hidden>
8
+ <div class="resize-handle resize-panel" data-resize="panel" role="separator" aria-orientation="vertical"
9
+ aria-controls="ask-panel" aria-label="{{ _('Resize the Ask panel') }}"
10
+ title="{{ _('Drag to resize; double-click to reset') }}" tabindex="0"></div>
11
+ <div class="ask-head">
12
+ <h2 id="ask-title">{{ _("Ask about the code") }}</h2>
13
+ <button type="button" class="icon-button" data-ask-close aria-label="{{ _('Close') }}">{{ icon("close") }}</button>
14
+ </div>
15
+ <div class="ask-thread" data-ask-thread aria-live="polite">
16
+ <p class="ask-intro" data-ask-intro>{% if page is defined and page.id %}{{ _("Ask anything about the code. Your question goes with the page you're reading. Save the answers worth keeping.") }}{% else %}{{ _("Ask anything about the code. Save the answers worth keeping.") }}{% endif %}</p>
17
+ </div>
18
+ <form class="ask-form" data-ask-form>
19
+ <label class="visually-hidden" for="ask-question">{{ _("Your question") }}</label>
20
+ <textarea id="ask-question" name="question" rows="3" maxlength="4000" required placeholder="{{ _('For example: how does a request reach the database?') }}"></textarea>
21
+ <div class="ask-form-row">
22
+ <p class="ask-status" data-ask-status data-label-reading="{{ _('The assistant is reading the code…') }}"
23
+ data-label-used="{{ _('Used') }}" data-label-tokens="{{ _('tokens') }}"></p>
24
+ <button type="submit" class="button primary">{{ _("Send") }} {{ cost(estimates.get("answer")) }}</button>
25
+ </div>
26
+ </form>
27
+ </aside>
@@ -0,0 +1,32 @@
1
+ {% from "layout/icons.html" import icon %}
2
+ <header class="topbar">
3
+ <button type="button" class="icon-button sidebar-toggle" data-sidebar-toggle aria-controls="sidebar" aria-expanded="true"
4
+ aria-label="{{ _('Hide the sidebar') }}" data-label-hide="{{ _('Hide the sidebar') }}"
5
+ data-label-show="{{ _('Show the sidebar') }}">{{ icon("sidebar") }}</button>
6
+ <button type="button" class="icon-button menu-button" data-menu aria-controls="sidebar" aria-expanded="false" aria-label="{{ _('Menu') }}">{{ icon("menu") }}</button>
7
+ <a class="brand" href="/"><img src="/static/brand/mark.svg" alt="" width="24" height="24"><span>codetrail</span></a>
8
+ <span class="target" lang="en" dir="ltr">{{ target }}{% if nav.branch or nav.commit %}<span class="target-meta">{{ nav.branch }}{% if nav.commit %} · {{ nav.commit[:7] }}{% endif %}</span>{% endif %}</span>
9
+ <div class="topbar-search">
10
+ <form class="search-box" action="/search" method="get" role="search" data-search-form>
11
+ {{ icon("search") }}
12
+ <input type="search" name="q" placeholder="{{ _('Search or ask…') }}" aria-label="{{ _('Search the guide') }}" autocomplete="off" maxlength="1000">
13
+ <kbd class="search-hint" aria-hidden="true">⌘K</kbd>
14
+ </form>
15
+ </div>
16
+ <div class="topbar-actions">
17
+ <button type="button" class="button" data-ask-open>{{ icon("ask") }}<span>{{ _("Ask") }}</span></button>
18
+ <button type="button" class="icon-button" data-theme-toggle
19
+ data-label-system="{{ _('Theme: system (switch to light)') }}"
20
+ data-label-light="{{ _('Theme: light (switch to dark)') }}"
21
+ data-label-dark="{{ _('Theme: dark (switch to system)') }}"
22
+ aria-label="{{ _('Switch theme') }}">{{ icon("sun") }}</button>
23
+ <form class="language" data-language-form>
24
+ <label><span class="visually-hidden">{{ _("Language") }}</span>
25
+ <select name="language">
26
+ {% for option in languages %}<option value="{{ option.code }}" {% if option.code == language.code %}selected{% endif %}>{{ option.name }}</option>{% endfor %}
27
+ </select>
28
+ </label>
29
+ </form>
30
+ <button type="button" class="icon-button" data-shortcuts-open aria-label="{{ _('Keyboard shortcuts') }}">{{ icon("keyboard") }}</button>
31
+ </div>
32
+ </header>