ambitgraph 0.4.0__tar.gz → 0.5.0__tar.gz
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.
- {ambitgraph-0.4.0/ambitgraph.egg-info → ambitgraph-0.5.0}/PKG-INFO +1 -1
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/export.py +6 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/graph.py +73 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/langmirror.py +92 -3
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/page.py +62 -1
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/registry.py +101 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/serve.py +31 -1
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/theme.py +4 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/unitpage.py +21 -5
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/cli.py +15 -12
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/codelib.py +60 -2
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/edges.py +547 -2
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/langs.py +50 -15
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/portal.py +42 -2
- ambitgraph-0.5.0/ambitgraph/prompts.py +88 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/shell.py +145 -28
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/symbols.py +729 -2
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/urcode.py +11 -20
- {ambitgraph-0.4.0 → ambitgraph-0.5.0/ambitgraph.egg-info}/PKG-INFO +1 -1
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph.egg-info/SOURCES.txt +1 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/pyproject.toml +1 -1
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/LICENSE +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/MANIFEST.in +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/README.md +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/__init__.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/__main__.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/ambit-map-enrich.md +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/ambit-mapllm-contract.md +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/cli.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/daemon.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/dialects/__init__.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/dialects/contract.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/dialects/express_pack.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/dialects/fastapi_pack.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/dialects/flask_pack.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/dialects/nextjs_pack.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/dialects/supabase_pack.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/families.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/island_layout.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/islands.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/judged.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/kitdemo.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/library.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/load.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/mapllm.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/matched.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/model.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/objects.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/orchestrate.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambit_map/paths.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/WALKTHROUGH.md +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/__init__.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/__main__.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/cockpit.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/config.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/console.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/demo.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/docs_discovery.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/doctor.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/duplication.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/emit.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/engine_identity.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/estimate.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/eval.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/feeds.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/gate.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/gate_check.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/gate_server.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/harvest.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/inventory.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/jscalls.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/mds.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/merge.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/merge_jsp.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/parameterize.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/pipeline.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/pycalls.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/ratify.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/report.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/reports.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/sample.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/secretary.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/stages.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/tables.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph/tasks.py +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph.egg-info/dependency_links.txt +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph.egg-info/entry_points.txt +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph.egg-info/requires.txt +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/ambitgraph.egg-info/top_level.txt +0 -0
- {ambitgraph-0.4.0 → ambitgraph-0.5.0}/setup.cfg +0 -0
|
@@ -23,6 +23,10 @@ GRAPH_EXPORT_CAP = 200 # the brief's cap
|
|
|
23
23
|
EXPORT_INDEX = "index.html"
|
|
24
24
|
EXPORT_MAP = reg.EXPORT_MAP_REL
|
|
25
25
|
EXPORT_LIBRARY = "library.html"
|
|
26
|
+
# The Whole map tab. NOT map.html -- that is the default GRAPH seed page
|
|
27
|
+
# and every existing link plus the manifest resolve to it; renaming it to
|
|
28
|
+
# free the nicer name would break links to fix an aesthetic.
|
|
29
|
+
EXPORT_OVERVIEW = reg.EXPORT_OVERVIEW_REL
|
|
26
30
|
MANIFEST_NAME = "ambitgraph-map.export.json" # U4 review fix (blocker): the
|
|
27
31
|
# scoped-prune manifest -- lists every relative path THIS export wrote, so
|
|
28
32
|
# the NEXT export deletes only what it itself owns, never a hand-placed file.
|
|
@@ -139,6 +143,8 @@ def page_rows(model, dialects):
|
|
|
139
143
|
# existing link and the manifest resolve to it. A real row now, not a copy.
|
|
140
144
|
rows.append(Row(EXPORT_MAP, "/graph", index_query, 0))
|
|
141
145
|
|
|
146
|
+
rows.append(Row(EXPORT_OVERVIEW, "/overview", "", 0))
|
|
147
|
+
|
|
142
148
|
routes_by_uid = lib._routes_by_uid(dialects)
|
|
143
149
|
units_iter = model.units.values() if hasattr(model, "units") else ()
|
|
144
150
|
order = fam.families_in_use(units_iter, routes_by_uid)
|
|
@@ -1972,3 +1972,76 @@ GRAPH_JS = '''
|
|
|
1972
1972
|
box.scrollTop = (H - box.clientHeight) / 2;
|
|
1973
1973
|
})();
|
|
1974
1974
|
'''
|
|
1975
|
+
|
|
1976
|
+
|
|
1977
|
+
# ---------------------------------------------------------------------------
|
|
1978
|
+
# 3.16 THE ISLANDS RENDERER (islands-render wave, 2026-09-05) -- the Whole
|
|
1979
|
+
# map tab's drawing half. COMPOSES AND DRAWS ONLY: island_layout.py already
|
|
1980
|
+
# decided every position (its own C7); this function's one piece of
|
|
1981
|
+
# arithmetic is pack_islands's own documented contract -- every island's
|
|
1982
|
+
# placement is normalized into the [0,1] whole-map unit square, so
|
|
1983
|
+
# composing means multiplying by a fixed pixel side, never recomputing a
|
|
1984
|
+
# bounding box or re-deriving a layout.
|
|
1985
|
+
#
|
|
1986
|
+
# REUSES the Graph tab's pan/zoom mechanism byte-for-byte: GRAPH_JS (drag
|
|
1987
|
+
# to pan, the three zoom buttons, SVG width/height-attribute zoom -- never
|
|
1988
|
+
# viewBox alone, the exact trap a design-panel review caught before this
|
|
1989
|
+
# was written) and the SAME .cm-canvas/.cm-canvas-wrap/.cm-zoom-ctrl
|
|
1990
|
+
# classes/button ids, so the script needs no adaptation. An island node
|
|
1991
|
+
# carries no edges (islands.py's own C5), so GRAPH_JS's edge-click/.cm-desc
|
|
1992
|
+
# half simply never fires here -- it is not wired to anything, and nothing
|
|
1993
|
+
# breaks by its absence (every DOM lookup in GRAPH_JS null-checks first).
|
|
1994
|
+
# ---------------------------------------------------------------------------
|
|
1995
|
+
|
|
1996
|
+
|
|
1997
|
+
def render_islands_canvas(layout):
|
|
1998
|
+
"""The `<div class="cm-canvas-wrap">...` markup for a DRAWABLE
|
|
1999
|
+
product-layout.json payload (the caller decides drawability -- this
|
|
2000
|
+
function assumes `layout["islands"]` is non-empty and never checks)."""
|
|
2001
|
+
side = reg.ISLANDS_CANVAS_SIDE
|
|
2002
|
+
pad = reg.ISLANDS_CANVAS_PADDING
|
|
2003
|
+
w = side + 2 * pad
|
|
2004
|
+
h = w
|
|
2005
|
+
|
|
2006
|
+
boundary_parts = []
|
|
2007
|
+
node_parts = []
|
|
2008
|
+
for isl in layout.get("islands", ()):
|
|
2009
|
+
bx = isl["x"] * side + pad
|
|
2010
|
+
by = isl["y"] * side + pad
|
|
2011
|
+
bs = isl["scale"] * side
|
|
2012
|
+
# A subtle region under the nodes, never a hard border or a label
|
|
2013
|
+
# (design-panel finding: pack_islands shelves islands with zero
|
|
2014
|
+
# gap, so shared placement alone can read as one cluster when it
|
|
2015
|
+
# is two touching ones).
|
|
2016
|
+
boundary_parts.append(
|
|
2017
|
+
'<rect class="isl-island" x="{x}" y="{y}" width="{s}" height="{s}" '
|
|
2018
|
+
'rx="6"></rect>'.format(x=_n(bx), y=_n(by), s=_n(bs)))
|
|
2019
|
+
for node in isl.get("nodes", ()):
|
|
2020
|
+
gx = bx + node["x"] * bs
|
|
2021
|
+
gy = by + node["y"] * bs
|
|
2022
|
+
degree = node.get("d", 0)
|
|
2023
|
+
r = min(reg.ISL_NODE_RADIUS_MAX,
|
|
2024
|
+
reg.ISL_NODE_RADIUS_MIN + degree * reg.ISL_NODE_RADIUS_PER_DEGREE)
|
|
2025
|
+
node_parts.append(
|
|
2026
|
+
'<a href="{href}"><circle class="isl-node" cx="{cx}" cy="{cy}" '
|
|
2027
|
+
'r="{r}"></circle></a>'.format(
|
|
2028
|
+
href=reg.href_unit(node["u"]), cx=_n(gx), cy=_n(gy), r=_n(r)))
|
|
2029
|
+
|
|
2030
|
+
svg = (
|
|
2031
|
+
'<svg class="isl-svg" viewBox="0 0 {w} {h}" width="{w}" height="{h}" '
|
|
2032
|
+
'data-zoom="1" role="img" aria-label="{aria}" data-amg-object="islands">'
|
|
2033
|
+
'<g class="isl-islands">{boundaries}</g>'
|
|
2034
|
+
'<g class="isl-nodes">{nodes}</g></svg>'
|
|
2035
|
+
).format(w=_n(w), h=_n(h), aria=reg.esc(reg.OVERVIEW_CANVAS_LABEL),
|
|
2036
|
+
boundaries="".join(boundary_parts), nodes="".join(node_parts))
|
|
2037
|
+
|
|
2038
|
+
return (
|
|
2039
|
+
'<div class="cm-canvas-wrap"><div class="cm-zoom-ctrl">'
|
|
2040
|
+
'<button type="button" id="cmZoomOut" title="{zo}" aria-label="{zo}">{zog}</button>'
|
|
2041
|
+
'<button type="button" id="cmZoomReset" title="{zr}" aria-label="{zr}">{zrg}</button>'
|
|
2042
|
+
'<button type="button" id="cmZoomIn" title="{zi}" aria-label="{zi}">{zig}</button>'
|
|
2043
|
+
'</div><div class="cm-canvas" data-canvas="1">{svg}</div></div>'
|
|
2044
|
+
).format(zo=reg.esc(reg.GRAPH_ZOOM_OUT_LABEL), zog=reg.esc(reg.GRAPH_ZOOM_OUT_GLYPH),
|
|
2045
|
+
zr=reg.esc(reg.GRAPH_ZOOM_RESET_LABEL), zrg=reg.esc(reg.GRAPH_ZOOM_RESET_GLYPH),
|
|
2046
|
+
zi=reg.esc(reg.GRAPH_ZOOM_IN_LABEL), zig=reg.esc(reg.GRAPH_ZOOM_IN_GLYPH),
|
|
2047
|
+
svg=svg)
|
|
@@ -118,6 +118,15 @@ JAVA_LANGS = ("java",)
|
|
|
118
118
|
# count gate.
|
|
119
119
|
C_LANGS = ("c",)
|
|
120
120
|
|
|
121
|
+
# RB_LANGS (Ruby wave 1): the identical one-tag grouping shape as
|
|
122
|
+
# PY_LANGS/JAVA_LANGS/C_LANGS -- "rb" is the only tag ambitgraph/langs.py's
|
|
123
|
+
# "rb" LangSpec ever declares (naming_exts {".rb"}), so this carries no
|
|
124
|
+
# reader-tolerance member either. Read by kind_key's include branch, which
|
|
125
|
+
# maps a resolved `require_relative` onto the include/static row (see
|
|
126
|
+
# registry.py's own comment there), and by the unit story's type-kind
|
|
127
|
+
# count gate.
|
|
128
|
+
RB_LANGS = ("rb",)
|
|
129
|
+
|
|
121
130
|
# TYPE_SYMBOL_KINDS (Java W1 repair -- the wall-legal PRODUCT-side
|
|
122
131
|
# mirror of ambitgraph/codelib.py's own _TYPE_SYMBOL_KINDS): the literal
|
|
123
132
|
# kind strings symbols.py's own class alternation emits for a Java type
|
|
@@ -149,6 +158,26 @@ TYPE_SYMBOL_KINDS = ("class", "interface", "enum", "record")
|
|
|
149
158
|
# never a cross-wall import.
|
|
150
159
|
C_TYPE_SYMBOL_KINDS = ("struct", "union", "enum", "typedef")
|
|
151
160
|
|
|
161
|
+
# RB_TYPE_SYMBOL_KINDS (Ruby wave 1 -- the wall-legal PRODUCT-side mirror
|
|
162
|
+
# of ambitgraph/codelib.py's own _RB_TYPE_SYMBOL_KINDS): the literal kind
|
|
163
|
+
# strings extract_ruby_symbols emits for a Ruby scope-opener declaration --
|
|
164
|
+
# "class" and "module", the two keywords _RB_SCOPE_LINE_RX matches. Unlike
|
|
165
|
+
# TYPE_SYMBOL_KINDS/C_TYPE_SYMBOL_KINDS above, this set already includes
|
|
166
|
+
# "class" itself (Ruby genuinely has classes), so it feeds BOTH halves of
|
|
167
|
+
# codelib.py's carve identically -- the same shape TYPE_SYMBOL_KINDS takes
|
|
168
|
+
# for Java, never C's shape (which unions "class" in separately because C
|
|
169
|
+
# never emits that literal kind at all). Consumed by
|
|
170
|
+
# ambit_map/unitpage.py's class-count bucket under the RB_LANGS gate
|
|
171
|
+
# above, WIDENING THE COUNT ONLY -- the word stays reg.UNIT_STORY_CLASSES
|
|
172
|
+
# for Ruby (unitpage.py's own class_word ternary is not gated on RB_LANGS
|
|
173
|
+
# at all): Ruby genuinely has classes, so mirroring C's word-swap here
|
|
174
|
+
# would render "N type(s)" about a language whose classes are exactly what
|
|
175
|
+
# the count is naming. Diffed against ambitgraph.codelib._RB_TYPE_SYMBOL_KINDS
|
|
176
|
+
# by tests/test_langs_mirror_diff.py (DIFFED_FACT_NAMES entry
|
|
177
|
+
# "rb_type_symbol_kinds") -- one origin (symbols.py's own kind literals),
|
|
178
|
+
# two independently-declared copies, never a cross-wall import.
|
|
179
|
+
RB_TYPE_SYMBOL_KINDS = ("class", "module")
|
|
180
|
+
|
|
152
181
|
# NAMESPACE_LANGS (Java W1 repair -- the wall-legal PRODUCT-side mirror of
|
|
153
182
|
# ambitgraph/langs.py's own NAMESPACE_LANGS): the `lang` tags whose units
|
|
154
183
|
# get the java-namespace treatment (the languages whose LangSpec declares
|
|
@@ -220,7 +249,13 @@ KIND_READER_LANGS = {
|
|
|
220
249
|
# include/static's word for word. A C repo's include reader is
|
|
221
250
|
# therefore named as having run, where a missing member would report
|
|
222
251
|
# the include plane unmeasured on every C repo forever.
|
|
223
|
-
"
|
|
252
|
+
# "rb" joins this row the same way (Ruby wave 1): a resolved
|
|
253
|
+
# `require_relative` IS a static include -- a literal path read from
|
|
254
|
+
# the source text, spliced in at include time, never varying at
|
|
255
|
+
# runtime -- the identical claim this row already makes. Without the
|
|
256
|
+
# member a Ruby repo's 236-edge include lane would report the include
|
|
257
|
+
# plane unmeasured despite having just drawn every one of them.
|
|
258
|
+
"include/static": frozenset({"jsp", "c", "rb"}),
|
|
224
259
|
"include/import.py": frozenset({"py"}),
|
|
225
260
|
"include/import.js": frozenset({"js"}),
|
|
226
261
|
# Java W1 (2026-09-02), additive: langs.py's java LangSpec gains
|
|
@@ -254,7 +289,10 @@ KIND_READER_LANGS = {
|
|
|
254
289
|
# without the member a C repo would be told the calls reader never
|
|
255
290
|
# heard of C, the exact defect this table's own NAMESPACE_LANGS
|
|
256
291
|
# comment records for java.
|
|
257
|
-
"
|
|
292
|
+
# "rb" joins this row the same way (Ruby wave 1): langs.py's rb
|
|
293
|
+
# LangSpec declares call_lane=True and edges.py builds Ruby its own
|
|
294
|
+
# definer pool, landed independently of this stage.
|
|
295
|
+
"calls": frozenset({"jsp", "java", "py", "js", "c", "rb"}),
|
|
258
296
|
"reference/script": frozenset({"jsp"}),
|
|
259
297
|
"route/mount": frozenset({"py", "js"}),
|
|
260
298
|
"ui/component-use": frozenset({"js"}),
|
|
@@ -278,7 +316,7 @@ KIND_READER_LANGS = {
|
|
|
278
316
|
# ===========================================================================
|
|
279
317
|
_GRAMMAR_EXTS = frozenset({
|
|
280
318
|
".jsp", ".jspf", ".java", ".js", ".mjs", ".cjs", ".jsx", ".ts", ".tsx",
|
|
281
|
-
".py", ".sh", ".sql", ".c", ".h",
|
|
319
|
+
".py", ".sh", ".sql", ".c", ".h", ".rb",
|
|
282
320
|
})
|
|
283
321
|
|
|
284
322
|
|
|
@@ -322,6 +360,9 @@ _GRAMMAR_EXTS = frozenset({
|
|
|
322
360
|
# ===========================================================================
|
|
323
361
|
_JSP_JAVA = frozenset({"jsp", "jspf", "java"})
|
|
324
362
|
_C = frozenset({"c"})
|
|
363
|
+
# _RB (Ruby wave 1): the identical one-tag alias shape as _C above -- "rb"
|
|
364
|
+
# is the only tag any Ruby-declared sentence ever carries this wave.
|
|
365
|
+
_RB = frozenset({"rb"})
|
|
325
366
|
BLIND_SENTENCE_LANGS = (
|
|
326
367
|
("Java declarations inside JSP scriptlet blocks", _JSP_JAVA),
|
|
327
368
|
("A Java class whose modifiers are written on their own line", _JSP_JAVA),
|
|
@@ -362,6 +403,54 @@ BLIND_SENTENCE_LANGS = (
|
|
|
362
403
|
("A quoted C include with no candidate anywhere in this scan", _C),
|
|
363
404
|
("A quoted C include this scan does hold a file for, but position "
|
|
364
405
|
"does not license,", _C),
|
|
406
|
+
# Ruby wave 1's own two STATIC, structural gaps (ambitgraph.symbols.
|
|
407
|
+
# BLIND_SENTENCES), anchored on their opening words -- the same
|
|
408
|
+
# shape as the C declaration grammar's own gaps just above.
|
|
409
|
+
("A Ruby heredoc body, from its opener line", _RB),
|
|
410
|
+
("A Ruby percent literal (", _RB),
|
|
411
|
+
# Ruby wave 1's seven PER-SCAN symbol-grammar sentences
|
|
412
|
+
# (ambitgraph.symbols.rb_symbol_blind_sentences), anchored on the
|
|
413
|
+
# fixed prefix constants declared beside that function -- retyped
|
|
414
|
+
# here by value, exactly as the C include lane's four prefixes are
|
|
415
|
+
# above.
|
|
416
|
+
("A method definition line whose name does not fit any Ruby method "
|
|
417
|
+
"name shape is refused outright", _RB),
|
|
418
|
+
("A method defined directly on one specific runtime object, or a "
|
|
419
|
+
"singleton class opened on an object other than self, is refused",
|
|
420
|
+
_RB),
|
|
421
|
+
("A private, protected, or module_function marker changes the "
|
|
422
|
+
"default visibility of every method written after it, a stateful "
|
|
423
|
+
"effect this wave discloses rather than simulates", _RB),
|
|
424
|
+
("An attr_accessor, attr_reader, or attr_writer call defines one or "
|
|
425
|
+
"more methods by name that this wave does not read as symbols",
|
|
426
|
+
_RB),
|
|
427
|
+
("A define_method, method_missing, class_eval, instance_eval, "
|
|
428
|
+
"alias, or alias_method call can define or rename a method by a "
|
|
429
|
+
"name this wave cannot predict", _RB),
|
|
430
|
+
("A class, module, or method opener sharing its physical line with "
|
|
431
|
+
"another statement, separated by a semicolon, is read for its own "
|
|
432
|
+
"name only", _RB),
|
|
433
|
+
("A method definition found indented with no enclosing class, "
|
|
434
|
+
"module, or singleton scope on the stack sits inside a block "
|
|
435
|
+
"construct this wave does not track", _RB),
|
|
436
|
+
# Ruby wave 1's six PER-SCAN edges.py sentences (the include lane's
|
|
437
|
+
# three, ambitgraph.edges.rb_include_blind_sentences, plus the call
|
|
438
|
+
# ceiling's three, ambitgraph.edges.rb_call_blind_sentences),
|
|
439
|
+
# anchored on their own fixed prefix constants, same discipline.
|
|
440
|
+
("A require naming a plain load-path string, rather than a "
|
|
441
|
+
"relative file, searches the Ruby runtime load path and is never "
|
|
442
|
+
"drawn as an edge", _RB),
|
|
443
|
+
("A require_relative naming a file this scan does hold somewhere, "
|
|
444
|
+
"but not at the position this call computes to, draws no edge",
|
|
445
|
+
_RB),
|
|
446
|
+
("A require_relative naming no file anywhere in this scan draws no "
|
|
447
|
+
"edge", _RB),
|
|
448
|
+
("Ruby calls need neither a receiver nor parentheses, so an EXACT "
|
|
449
|
+
"count of receiverless, parenless call sites is impossible", _RB),
|
|
450
|
+
("A method whose name is a setter or an operator can never be "
|
|
451
|
+
"reached by either call site shape this lane recognizes", _RB),
|
|
452
|
+
("A method name defined in more than one place is dropped from the "
|
|
453
|
+
"call resolution pool entirely rather than guessed at", _RB),
|
|
365
454
|
)
|
|
366
455
|
|
|
367
456
|
|
|
@@ -14,7 +14,13 @@ from . import objects as ob
|
|
|
14
14
|
from . import unitpage as up
|
|
15
15
|
|
|
16
16
|
DEFAULT_SCREEN = "library"
|
|
17
|
-
|
|
17
|
+
# Broad to narrow, and the order the ruling itself listed: the whole shape,
|
|
18
|
+
# then one file's neighbourhood, then the inventory. "ic-grid" is REUSED from
|
|
19
|
+
# the kit (four separated blocks for a view of separate regions) -- it was
|
|
20
|
+
# used only by the internal kit-demo page, so no customer surface is made
|
|
21
|
+
# ambiguous, and the sprite's pinned token order is left alone.
|
|
22
|
+
RAIL = (("overview", "/overview", reg.TAB_WHOLE_MAP, "ic-grid"),
|
|
23
|
+
("graph", "/graph", "Graph", "ic-node"),
|
|
18
24
|
("library", "/library", "Library", "ic-layers"))
|
|
19
25
|
|
|
20
26
|
|
|
@@ -103,6 +109,61 @@ def render_page(model, now_iso, screens=None, active=None, q="",
|
|
|
103
109
|
return ob.document("Library", inner, "library", RAIL)
|
|
104
110
|
|
|
105
111
|
|
|
112
|
+
OVERVIEW_ABSENT = "absent" # no artifact was written for this run
|
|
113
|
+
OVERVIEW_STALE = "stale" # an artifact exists but no longer matches the code
|
|
114
|
+
OVERVIEW_EMPTY = "empty" # a current artifact exists but names zero islands
|
|
115
|
+
OVERVIEW_DRAWABLE = "drawable" # a current artifact exists and has islands to draw
|
|
116
|
+
|
|
117
|
+
_OVERVIEW_SENTENCES = {
|
|
118
|
+
OVERVIEW_ABSENT: reg.OVERVIEW_NO_LAYOUT,
|
|
119
|
+
OVERVIEW_STALE: reg.OVERVIEW_LAYOUT_STALE,
|
|
120
|
+
OVERVIEW_EMPTY: reg.OVERVIEW_LAYOUT_EMPTY,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def render_overview_page(model, now_iso, layout=None, dialects=None, state=OVERVIEW_ABSENT):
|
|
125
|
+
"""The Whole map tab -- the islands view (founder ruling, 2026-08-29;
|
|
126
|
+
the drawing half, islands-render wave, 2026-09-05).
|
|
127
|
+
|
|
128
|
+
`state` is the caller's own fail-closed classification of THIS run's
|
|
129
|
+
`product-layout.json` (serve.py's dispatch computes it via
|
|
130
|
+
`islands.read_product_layout` + `islands.layout_is_fresh`) -- one of
|
|
131
|
+
the four module-level constants above. `layout` carries the REAL
|
|
132
|
+
payload dict ONLY when `state == OVERVIEW_DRAWABLE`; every other state
|
|
133
|
+
keeps `layout=None`, because there is nothing to compose and this
|
|
134
|
+
function must never receive position data it isn't about to draw.
|
|
135
|
+
|
|
136
|
+
Four honest states, never conflated: no artifact was written
|
|
137
|
+
(ABSENT); one exists but the code has changed since (STALE -- drawn
|
|
138
|
+
NEVER, a picture from a population that no longer matches the code on
|
|
139
|
+
disk would be exactly the guess-with-a-progress-bar this product
|
|
140
|
+
refuses to ship); one is current but connects nothing (EMPTY -- a
|
|
141
|
+
real, checked state, never a blank canvas a reader could mistake for
|
|
142
|
+
a bug); one is current and has islands (DRAWABLE -- the only state
|
|
143
|
+
that composes and draws `graph.render_islands_canvas(layout)`,
|
|
144
|
+
computing no layout of its own, per island_layout.py's own C7).
|
|
145
|
+
"""
|
|
146
|
+
if state == OVERVIEW_DRAWABLE and layout is None:
|
|
147
|
+
raise ValueError("state=drawable requires the real layout payload")
|
|
148
|
+
if state != OVERVIEW_DRAWABLE and layout is not None:
|
|
149
|
+
raise ValueError("layout must be None unless state=drawable")
|
|
150
|
+
# cased=False: ob.hero runs title_case() by default, which rendered the
|
|
151
|
+
# founder's ruled phrase as "Whole Map". His words are not ours to
|
|
152
|
+
# re-case -- the same U1-D hazard the hero's own docstring warns about
|
|
153
|
+
# for a reader's search query. The tests passed while the PAGE was
|
|
154
|
+
# wrong, because they pinned the constant and not the render.
|
|
155
|
+
hero = ob.hero([("AmbitGraph", "/library"), (reg.TAB_WHOLE_MAP, None)],
|
|
156
|
+
reg.KICKER_WHOLE_MAP, reg.TAB_WHOLE_MAP, "", cased=False)
|
|
157
|
+
if state == OVERVIEW_DRAWABLE:
|
|
158
|
+
body = gph.render_islands_canvas(layout)
|
|
159
|
+
else:
|
|
160
|
+
body = ob.note(reg.esc(_OVERVIEW_SENTENCES[state]))
|
|
161
|
+
inner = ob.block("overview", reg.OVERVIEW_CANVAS_LABEL, body,
|
|
162
|
+
shown=True, cased=False)
|
|
163
|
+
return ob.document(reg.TAB_WHOLE_MAP, hero + inner, "overview", RAIL,
|
|
164
|
+
extra_js=(gph.GRAPH_JS if state == OVERVIEW_DRAWABLE else ""))
|
|
165
|
+
|
|
166
|
+
|
|
106
167
|
def render_damaged_page(damaged, now_iso):
|
|
107
168
|
"""One honest document: no active tab (there is nothing to switch to
|
|
108
169
|
and a lit tab would be a lie), one hero, one block."""
|
|
@@ -121,6 +121,11 @@ JAVA_LANGS = langm.JAVA_LANGS
|
|
|
121
121
|
# and unitpage.py's class-count bucket reads it as the gate on
|
|
122
122
|
# C_TYPE_SYMBOL_KINDS below.
|
|
123
123
|
C_LANGS = langm.C_LANGS
|
|
124
|
+
# The fifth src_lang bucket, same alias-only shape as the four above:
|
|
125
|
+
# kind_key's include branch reads it to route a resolved Ruby
|
|
126
|
+
# require_relative, and unitpage.py's class-count bucket reads it as the
|
|
127
|
+
# gate on RB_TYPE_SYMBOL_KINDS below.
|
|
128
|
+
RB_LANGS = langm.RB_LANGS
|
|
124
129
|
# Java W1 repair (2026-09-02), additive: the same alias-only
|
|
125
130
|
# shape, for unitpage.py's own class-count bucket -- see langmirror.py's
|
|
126
131
|
# own comment on this field for the producer/consumer/diff story.
|
|
@@ -130,6 +135,13 @@ TYPE_SYMBOL_KINDS = langm.TYPE_SYMBOL_KINDS
|
|
|
130
135
|
# field for the producer/consumer/diff story, and for why "enum" being a
|
|
131
136
|
# member of both sets is safe (the two language gates are disjoint).
|
|
132
137
|
C_TYPE_SYMBOL_KINDS = langm.C_TYPE_SYMBOL_KINDS
|
|
138
|
+
# The Ruby half of the same bucket -- the kind strings extract_ruby_symbols
|
|
139
|
+
# emits for a scope-opener declaration ("class"/"module"); see
|
|
140
|
+
# langmirror.py's own comment on this field for the producer/consumer/diff
|
|
141
|
+
# story. Unlike C_TYPE_SYMBOL_KINDS, this set already includes "class"
|
|
142
|
+
# itself, so unitpage.py's count gate widens with it directly while its
|
|
143
|
+
# word stays on the CLASSES branch -- Ruby genuinely has classes.
|
|
144
|
+
RB_TYPE_SYMBOL_KINDS = langm.RB_TYPE_SYMBOL_KINDS
|
|
133
145
|
# Java W1 repair, additive: the language gate that
|
|
134
146
|
# TYPE_SYMBOL_KINDS' unitpage consumer needs -- same alias-only shape;
|
|
135
147
|
# see langmirror.py's own comment on this field.
|
|
@@ -530,6 +542,26 @@ def kind_key(plane, kind="", src_lang=""):
|
|
|
530
542
|
# union check is what holds the two together.
|
|
531
543
|
if kind == "include" and src_lang in C_LANGS:
|
|
532
544
|
return "include/static"
|
|
545
|
+
# Ruby wave 1: the Ruby include lane's own edge kind is
|
|
546
|
+
# "require_relative", never "include" or "static" -- it is
|
|
547
|
+
# the literal string ambitgraph/edges.py stamps onto a RESOLVED
|
|
548
|
+
# require_relative row (a HELD, never-drawn plain `require`
|
|
549
|
+
# never reaches this function at all: load.py shunts an
|
|
550
|
+
# unresolved row into unresolved_refs before an IncludeEdge is
|
|
551
|
+
# ever built, so "require" can never arrive here as a kind).
|
|
552
|
+
# It lands on the include/static row for the identical reason
|
|
553
|
+
# a C `#include` does: the target is a literal path read from
|
|
554
|
+
# the source text, resolved once at include time, never
|
|
555
|
+
# varying at runtime. The src_lang gate is what keeps the
|
|
556
|
+
# reuse honest, exactly as it does for the C arm above --
|
|
557
|
+
# without it, a foreign producer's edge spelled kind
|
|
558
|
+
# "require_relative" would inherit this row by accident.
|
|
559
|
+
# langmirror.KIND_READER_LANGS' include/static row carries
|
|
560
|
+
# "rb" beside "jsp"/"c" for the same reason, and
|
|
561
|
+
# tests/test_langs_mirror_diff.py's include-rows union check
|
|
562
|
+
# is what holds the two together.
|
|
563
|
+
if kind == "require_relative" and src_lang in RB_LANGS:
|
|
564
|
+
return "include/static"
|
|
533
565
|
if kind == "import":
|
|
534
566
|
if src_lang in PY_LANGS:
|
|
535
567
|
return "include/import.py"
|
|
@@ -1663,6 +1695,10 @@ _STATIC = contextvars.ContextVar("ambit_map_static_export", default=None)
|
|
|
1663
1695
|
# The default graph seed page's exported filename, spelled ONCE here so the
|
|
1664
1696
|
# rewriter and the writer cannot disagree about it.
|
|
1665
1697
|
EXPORT_MAP_REL = "map.html"
|
|
1698
|
+
# The Whole map tab's exported filename, spelled ONCE. export.py reads it
|
|
1699
|
+
# from here rather than keeping a second literal -- two spellings of one
|
|
1700
|
+
# filename is how a rewriter and a writer silently disagree.
|
|
1701
|
+
EXPORT_OVERVIEW_REL = "overview.html"
|
|
1666
1702
|
|
|
1667
1703
|
StaticCtx = namedtuple("StaticCtx", "depth graph_slugs foot_html")
|
|
1668
1704
|
|
|
@@ -1721,6 +1757,13 @@ def static_href(value, ctx):
|
|
|
1721
1757
|
return esc(p + "library/" + _famfile(f))
|
|
1722
1758
|
if path == "/search":
|
|
1723
1759
|
return esc(p + "library.html")
|
|
1760
|
+
if path == "/overview":
|
|
1761
|
+
# the Whole map tab. static_href is FAIL-CLOSED, so a rail link
|
|
1762
|
+
# with no mapping here does not become one dead link -- it raises
|
|
1763
|
+
# StaticHrefUnknown and REFUSES THE WHOLE EXPORT. That is the
|
|
1764
|
+
# right severity: the rail is on every page, so an unmappable tab
|
|
1765
|
+
# is a broken artifact, not a broken corner of one.
|
|
1766
|
+
return esc(p + EXPORT_OVERVIEW_REL)
|
|
1724
1767
|
if path == "/unit" and u is not None:
|
|
1725
1768
|
return esc(p + "unit/" + export_slug("unit", u) + ".html")
|
|
1726
1769
|
if path == "/table" and t is not None:
|
|
@@ -2839,6 +2882,64 @@ GRAMMARLESS_TABLE_SENTENCE = (
|
|
|
2839
2882
|
"statement can still appear here."
|
|
2840
2883
|
)
|
|
2841
2884
|
|
|
2885
|
+
# The Whole map tab (founder ruling, 2026-08-29). His ruled phrase, and it
|
|
2886
|
+
# invents no name: it is panel D's own existing headline wording ("Terrarium,
|
|
2887
|
+
# whole map: 186 connected files"), so the no-new-names law is satisfied by
|
|
2888
|
+
# CITATION rather than by argument. Shortening it to "Map" collides with
|
|
2889
|
+
# map.html's meaning and re-opens a question he already closed.
|
|
2890
|
+
TAB_WHOLE_MAP = "Whole map"
|
|
2891
|
+
# Kicker for the Whole map surface, in the register its siblings use
|
|
2892
|
+
# ("Code map", "Code library").
|
|
2893
|
+
KICKER_WHOLE_MAP = "Code map"
|
|
2894
|
+
# The canvas block's own label. Distinct from the page title on purpose:
|
|
2895
|
+
# "Whole map" was rendering three times on one page (crumb, hero, block).
|
|
2896
|
+
# Mirrors graph.py, whose canvas block keeps a stable label and puts the
|
|
2897
|
+
# no-edges note INSIDE it. "Islands" is the product's own word for the
|
|
2898
|
+
# object -- panel D's "6 separate islands".
|
|
2899
|
+
OVERVIEW_CANVAS_LABEL = "Islands"
|
|
2900
|
+
# The artifact-absent state. ABSENCE CLAIMS NOTHING, BUT A CHECK THAT RAN SAYS
|
|
2901
|
+
# IT CHECKED -- the same law W6's stranger clause runs on. The subject stays on
|
|
2902
|
+
# what this RUN did, never on the reader's codebase, and the way onward points
|
|
2903
|
+
# at a surface that actually exists (an offer whose target is unreachable is
|
|
2904
|
+
# worse than no offer).
|
|
2905
|
+
OVERVIEW_NO_LAYOUT = ("No whole-map layout was written for this run, so there is "
|
|
2906
|
+
"nothing to draw here. The Library lists every unit the "
|
|
2907
|
+
"scan read.")
|
|
2908
|
+
# The artifact-STALE state (islands-render wave, 2026-09-05): a run whose
|
|
2909
|
+
# code changed since its layout was computed is a DIFFERENT fact from one
|
|
2910
|
+
# that wrote nothing at all -- OVERVIEW_NO_LAYOUT is false of it too. A
|
|
2911
|
+
# stale artifact is drawn NEVER (a picture computed from a population that
|
|
2912
|
+
# no longer matches the code on disk would be exactly the guess-with-a-
|
|
2913
|
+
# progress-bar this product refuses to ship); this sentence names why the
|
|
2914
|
+
# tab shows nothing without claiming absence.
|
|
2915
|
+
OVERVIEW_LAYOUT_STALE = ("A whole-map layout exists for this run, but the code has "
|
|
2916
|
+
"changed since it was written, so this view will not draw "
|
|
2917
|
+
"a picture that no longer matches it. The Library lists "
|
|
2918
|
+
"every unit the current scan read.")
|
|
2919
|
+
# The artifact-DRAWABLE-BUT-EMPTY state: a fresh, current layout that
|
|
2920
|
+
# names zero islands (every unit isolated with no unit-to-unit records at
|
|
2921
|
+
# all, or none scanned). Still a real, checked state -- never a blank
|
|
2922
|
+
# <svg> a reader could mistake for a bug.
|
|
2923
|
+
OVERVIEW_LAYOUT_EMPTY = ("The whole-map layout for this run is current, but it "
|
|
2924
|
+
"connects nothing -- every unit the scan read stands "
|
|
2925
|
+
"alone. The Library lists them.")
|
|
2926
|
+
# The whole-map canvas is a fixed-size square (island_layout.pack_islands's
|
|
2927
|
+
# own contract: every island's placement is already normalized into the
|
|
2928
|
+
# [0,1] unit square -- composing means multiplying by this side length,
|
|
2929
|
+
# never recomputing a bounding box). Padding keeps edge-island dots off
|
|
2930
|
+
# the viewBox border.
|
|
2931
|
+
ISLANDS_CANVAS_SIDE = 720
|
|
2932
|
+
ISLANDS_CANVAS_PADDING = 24
|
|
2933
|
+
# Node radius floor and ceiling, in pixels: a degree-0 node (every
|
|
2934
|
+
# singleton island's only member) must never shrink toward invisible --
|
|
2935
|
+
# it is still a real, clickable unit page (islands-render panel finding).
|
|
2936
|
+
ISL_NODE_RADIUS_MIN = 3.0
|
|
2937
|
+
ISL_NODE_RADIUS_MAX = 11.0
|
|
2938
|
+
# Linear pixels-per-degree above the floor, capped at the ceiling -- no
|
|
2939
|
+
# global max-degree pass needed (one run's node sizes stay comparable to
|
|
2940
|
+
# another's, and the renderer stays a pure per-node function).
|
|
2941
|
+
ISL_NODE_RADIUS_PER_DEGREE = 0.6
|
|
2942
|
+
|
|
2842
2943
|
LABEL_SEARCH = "Find a function"
|
|
2843
2944
|
LABEL_GLANCE = "The library — at a glance"
|
|
2844
2945
|
LABEL_TABLES = "Busiest tables — the data the code leans on"
|
|
@@ -107,6 +107,7 @@ from pathlib import Path
|
|
|
107
107
|
from . import registry as reg
|
|
108
108
|
from . import library as lib
|
|
109
109
|
from . import graph as gph
|
|
110
|
+
from . import islands
|
|
110
111
|
from . import page as pg
|
|
111
112
|
from . import objects as ob
|
|
112
113
|
from . import unitpage as up
|
|
@@ -129,7 +130,7 @@ SERVER_VERSION = "AmbitGraph/1"
|
|
|
129
130
|
# copied by value from ambitgraph/console.py:66 -- ambit_map imports no
|
|
130
131
|
# company code (the ambit_telegraph/store.py precedent)
|
|
131
132
|
_ALLOWED_HOSTS = frozenset({"127.0.0.1", "localhost", "[::1]", "::1"})
|
|
132
|
-
_ROUTES = ("/", "/library", "/graph", "/search", "/unit", "/table",
|
|
133
|
+
_ROUTES = ("/", "/library", "/graph", "/overview", "/search", "/unit", "/table",
|
|
133
134
|
"/paths", "/table-closure", "/health", "/ping", "/stop")
|
|
134
135
|
# H4: named here, beside the route it gates, rather than as a literal
|
|
135
136
|
# buried in _handle_stop -- daemon.py (the launcher, a different module
|
|
@@ -637,6 +638,12 @@ def route(path, raw_query):
|
|
|
637
638
|
active = "stop"
|
|
638
639
|
elif path == "/graph":
|
|
639
640
|
active = "graph"
|
|
641
|
+
elif path == "/overview":
|
|
642
|
+
# the Whole map tab (founder ruling 2026-08-29). NOT "/map":
|
|
643
|
+
# map.html is already the default graph seed page and the export
|
|
644
|
+
# manifest points at it, so the LABEL carries his ruled phrase
|
|
645
|
+
# while the ROUTE stays plumbing that names nothing.
|
|
646
|
+
active = "overview"
|
|
640
647
|
elif path == "/search":
|
|
641
648
|
active = "search"
|
|
642
649
|
elif path == "/unit":
|
|
@@ -701,6 +708,29 @@ def render(state, r, now_iso, snapshot=False) -> str:
|
|
|
701
708
|
dialects=state.dialects,
|
|
702
709
|
stale=(bool(state.stale_reason) or state.pending_rebuild),
|
|
703
710
|
stale_text=(reg.STALE_TAG_ONCE if snapshot else reg.STALE_TAG_SERVE))
|
|
711
|
+
elif r["active"] == "overview":
|
|
712
|
+
# The four states are pg.render_overview_page's own to classify
|
|
713
|
+
# into a sentence or a drawing (islands-render wave, 2026-09-05).
|
|
714
|
+
# This dispatch's job is ONLY to read the three facts that decide
|
|
715
|
+
# which state applies: does an artifact exist, is it fresh
|
|
716
|
+
# against THIS run's own current population, does it name any
|
|
717
|
+
# islands. state.model/state.dialects/state.run_dir are read here
|
|
718
|
+
# the same unsynchronized way every other route already reads
|
|
719
|
+
# them (docs/ROADMAP.md names the file-wide hardening item; this
|
|
720
|
+
# route does not fix it alone).
|
|
721
|
+
data = islands.read_product_layout(state.run_dir)
|
|
722
|
+
if not data:
|
|
723
|
+
ov_state, ov_layout = pg.OVERVIEW_ABSENT, None
|
|
724
|
+
elif not islands.layout_is_fresh(
|
|
725
|
+
data, pth.assemble_table(state.model, state.dialects), state.model.unit_ids):
|
|
726
|
+
ov_state, ov_layout = pg.OVERVIEW_STALE, None
|
|
727
|
+
elif not data.get("islands"):
|
|
728
|
+
ov_state, ov_layout = pg.OVERVIEW_EMPTY, None
|
|
729
|
+
else:
|
|
730
|
+
ov_state, ov_layout = pg.OVERVIEW_DRAWABLE, data
|
|
731
|
+
doc = pg.render_overview_page(
|
|
732
|
+
state.model, now_iso, layout=ov_layout, dialects=state.dialects,
|
|
733
|
+
state=ov_state)
|
|
704
734
|
elif r["active"] == "search":
|
|
705
735
|
raw_q = r["q"] if isinstance(r["q"], str) else ""
|
|
706
736
|
inner = lib.render_search(state.model, r["q"], now_iso, uncap=r["uncap"],
|
|
@@ -324,6 +324,10 @@ p.note{color:var(--muted);font-size:.92rem;margin:0;max-width:80ch;}
|
|
|
324
324
|
.cm-zoom-ctrl button:hover{background:#e0e7ff;color:#1e40af;}
|
|
325
325
|
text.cm-name{font:500 13px ui-monospace,monospace;fill:#1e293b;}
|
|
326
326
|
text.cm-meta{font:11px system-ui,sans-serif;fill:#64748b;}
|
|
327
|
+
.isl-svg{display:block;}
|
|
328
|
+
.isl-island{fill:var(--soft-bg,#f8fafc);stroke:var(--border,#e2e8f0);stroke-width:1;}
|
|
329
|
+
.isl-node{fill:var(--accent,#2c3e50);}
|
|
330
|
+
.isl-svg a:hover .isl-node,.isl-svg a:focus .isl-node{fill:var(--accent-dark,#1a1a2e);}
|
|
327
331
|
text.cm-elbl{font:500 11px ui-monospace,monospace;paint-order:stroke;stroke:#ffffff;stroke-width:3px;}
|
|
328
332
|
.cm-desc{margin-top:10px;padding:11px 14px;border:1px solid var(--line);border-radius:10px;background:#f8fafc;color:var(--ink);font-size:.9rem;min-height:1.4em;}
|
|
329
333
|
.cm-form{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:10px;}
|
|
@@ -845,15 +845,31 @@ def render_unit(model, uid, now_iso, uncap=frozenset(), dialects=None, judged=No
|
|
|
845
845
|
# language renders byte-identically to before this branch existed.
|
|
846
846
|
java_ns = u.lang in reg.NAMESPACE_LANGS
|
|
847
847
|
c_ns = u.lang in reg.C_LANGS
|
|
848
|
+
# Ruby wave 1: rb_ns widens the COUNT only, never the word below --
|
|
849
|
+
# see that ternary's own comment for why. reg.RB_TYPE_SYMBOL_KINDS
|
|
850
|
+
# already contains "class" itself (Ruby's own type-kind set, unlike
|
|
851
|
+
# C's), so a Ruby unit's "class" symbols are counted twice over by
|
|
852
|
+
# this `or` (once by the bare `s.kind == "class"` clause, once by
|
|
853
|
+
# this one) -- harmless, `sum` over a boolean-per-symbol expression
|
|
854
|
+
# never double-counts the SAME symbol twice, since each symbol
|
|
855
|
+
# contributes at most one `1` regardless of how many of the three
|
|
856
|
+
# clauses it satisfies.
|
|
857
|
+
rb_ns = u.lang in reg.RB_LANGS
|
|
848
858
|
c = sum(1 for s in u.symbols
|
|
849
859
|
if s.kind == "class"
|
|
850
860
|
or (java_ns and s.kind in reg.TYPE_SYMBOL_KINDS)
|
|
851
|
-
or (c_ns and s.kind in reg.C_TYPE_SYMBOL_KINDS)
|
|
861
|
+
or (c_ns and s.kind in reg.C_TYPE_SYMBOL_KINDS)
|
|
862
|
+
or (rb_ns and s.kind in reg.RB_TYPE_SYMBOL_KINDS))
|
|
852
863
|
# ... and the WORD that bucket is counted under is decided by the
|
|
853
|
-
# SAME gate. C has no classes at all -- what it
|
|
854
|
-
# structs, unions, enums and typedefs -- so a C unit's
|
|
855
|
-
# sentence would otherwise state a construct the language
|
|
856
|
-
# have, about a file that cannot contain one.
|
|
864
|
+
# SAME gate -- for C only. C has no classes at all -- what it
|
|
865
|
+
# declares is structs, unions, enums and typedefs -- so a C unit's
|
|
866
|
+
# first sentence would otherwise state a construct the language
|
|
867
|
+
# does not have, about a file that cannot contain one. Ruby is the
|
|
868
|
+
# opposite case: it genuinely has classes (and modules, counted
|
|
869
|
+
# alongside them just above), so mirroring the C gate onto this
|
|
870
|
+
# word too would render "N type(s)" about a language whose classes
|
|
871
|
+
# are exactly what the count just named -- rb_ns is deliberately
|
|
872
|
+
# ABSENT from this ternary. Every other language, Ruby included,
|
|
857
873
|
# keeps reg.UNIT_STORY_CLASSES, the default, byte for byte.
|
|
858
874
|
class_word = reg.UNIT_STORY_TYPES if c_ns else reg.UNIT_STORY_CLASSES
|
|
859
875
|
other = len(u.symbols) - f - c
|
|
@@ -1423,6 +1423,13 @@ def build_cockpit_html(runs_dir, repo_root=None, demo: bool = False,
|
|
|
1423
1423
|
# run, no scan.json is simply no tab (#15).
|
|
1424
1424
|
from . import urcode as uc
|
|
1425
1425
|
urcode_result = uc.read_urcode(portal)
|
|
1426
|
+
prompts_result = None
|
|
1427
|
+
if portal is not None:
|
|
1428
|
+
# The Manager screen's standing prompts: data the coordinating
|
|
1429
|
+
# seat keeps current; read once per build, never raises — no
|
|
1430
|
+
# declared root is simply no block (#15).
|
|
1431
|
+
from . import prompts as pr
|
|
1432
|
+
prompts_result = pr.read_prompts(portal)
|
|
1426
1433
|
|
|
1427
1434
|
html = build_shell(runs, budget, events, library,
|
|
1428
1435
|
tasks=shell_tasks, demo=demo, depth=depth,
|
|
@@ -1433,7 +1440,8 @@ def build_cockpit_html(runs_dir, repo_root=None, demo: bool = False,
|
|
|
1433
1440
|
cockpit_event_available=cockpit_event_available,
|
|
1434
1441
|
secretary=secretary_result,
|
|
1435
1442
|
reports=reports_result,
|
|
1436
|
-
urcode=urcode_result
|
|
1443
|
+
urcode=urcode_result,
|
|
1444
|
+
prompts=prompts_result)
|
|
1437
1445
|
return _CockpitBuild(html, runs, events, portal_error=portal_error)
|
|
1438
1446
|
|
|
1439
1447
|
|
|
@@ -2509,17 +2517,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2509
2517
|
mo.add_argument("--messages", required=True)
|
|
2510
2518
|
mo.add_argument("--by", required=True)
|
|
2511
2519
|
mo.add_argument("--reason", default="")
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
help="the seat that spent it; the author IS the seat, "
|
|
2519
|
-
"so there is no --by — a seat may only report its own "
|
|
2520
|
-
"spend, and only against a grant that names it")
|
|
2521
|
-
sp.add_argument("--tokens", type=int, required=True)
|
|
2522
|
-
sp.add_argument("--turns", type=int, required=True)
|
|
2520
|
+
# The `spend` verb is GONE (Telegraph T16). It was the only writer of
|
|
2521
|
+
# the meter file kind, and it was never run: no `meter-*.jsonl` has
|
|
2522
|
+
# existed on this machine, and every grant that ever lived expired at
|
|
2523
|
+
# 0 tokens and 0 turns. A grant's BUDGET stays — it is the declared
|
|
2524
|
+
# ceiling, and the ceiling verdicts still test against it — but nothing
|
|
2525
|
+
# counts against that ceiling any more, and nothing ever did.
|
|
2523
2526
|
ex = tsub.add_parser("export", help="write the Household artifact the "
|
|
2524
2527
|
"Cockpit reads — one versioned JSON file composed "
|
|
2525
2528
|
"from the real ledger; the Cockpit reads this FILE "
|