"was macht diese datei?" on tox.ini, setup.cfg, .flake8, .editorconfig,
pytest.ini or .coveragerc went to the model, which wrote "Testverwendungen"
for tox.ini. These files now get an outline read from them like YAML, JSON
and TOML: each section with its keys and short values, a value continued
on indented lines as its list, bounded with "+N more". The automatic card
counts sections with the same reader, and .flake8, .coveragerc and
.pylintrc now count as INI files there too.
The outline also says what a well-known file is for, in one line after its
heading, when the name and the structure fit: pre-commit, GitHub workflows,
package.json, pyproject.toml, tox.ini, setup.cfg, Docker Compose, Read the
Docs, EditorConfig, tsconfig, flake8, pytest and coverage.py. Other files
get no such line. The heading no longer says "Read from the file:"; the
note at the end still does.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Coming back to a file or selection that already had turns showed "New
topic: <same file>. Earlier messages are not sent with these questions."
and sent none of them, so "Und was noch?" got "Ja, und noch." The divider
now reads "Back to: X. Earlier messages about it are sent again." (German
"Zurück zu: X. ..." for a German question), and the request carries the
earlier turns of that topic again. Turns of other topics still stay out.
A short follow-up right after a change of topic ("und", "mehr", "und was
noch?", "and?", "more") has no turn of its topic to follow. It is now
answered without the model: earlier messages were about another topic and
are not sent, please ask the full question, with the example questions of
a prompt that asks nothing.
The chat check for K17 now expects the return to send the file's own
earlier question and to say "Zurück zu".
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Every word of the open file counted as a name the prompt could ask about,
so "test" went to the model whenever the file contained the word, and the
model echoed it. Greetings and probe words (test, hallo, hi, ok, ja, danke
and the like) now never count as names unless one of them is exactly the
selection's own name.
The hint for such a prompt no longer offers "Ask the model": the model had
nothing to answer there.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
"Ask the model" under a listed, grounded or file answer put the model's
text in place of that turn, so the 11 listed callers became one sentence
naming one of them. The model's reply is now a turn of its own below the
listed one, with the same question and the line "Asked the local model".
Every answer the model wrote freely now says so under it, in the language
of the question ("Generated by the local model; it can be wrong."), also on
an explanation card without listed facts. The button under such an answer
reads "Ask again" instead of "Retry"; after an error it stays "Retry".
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
The new German sentence said "noch nicht", which the promise scan reads as
a promise; both languages now state the condition.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
The project switcher turned "?project=django-demo&workspace=galaxy" into
"?project=cbm", so a reload could open another workspace. The new
address now keeps the page-wide parameters App names (the workspace and
the walk bounds) and nothing of the old project, and the address always
names the workspace in view, also one carried over a switch or chosen
from storage. Browser Back and Forward between projects work as before.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
A short general question about a Galaxy selection ("was macht diese
Klasse?", "what does this do") now gets the shape of its explanation:
the listed facts in the language of the question and one model sentence,
checked for unknown names and unsupported claims (German forms included).
Questions that ask for detail still go to the model.
A prompt without a question ("test", "hallo", "?") is answered locally
with example questions for the selection or the open file.
"What does this file do" and "explain this file (in detail)" about a
YAML, JSON or TOML file are answered with an outline read from the file;
a workflow keeps its counted facts.
Every local answer offers "Ask the model".
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
The Behavior "Start" field of django-demo offered 62 operations in the
order the server returned them, with look-alikes such as six times
"database_backwards · django/db/migrations/operations/models.py", and
"handle · .../loaddata.py" could not be found.
The field now opens with a Suggested group (the start the page or the
projection chose, then the five best ranked flows in the server's order),
followed by every operation alphabetically by name and then path. Look-
alikes carry their class, "database_backwards (CreateModel)", with the
name first so the list still reads alphabetically; the line or the
position when the class does not tell them apart. Ranked flow starts have
no qualified name, so the look-alikes among them are named with one index
query by name and file, matched by node id; only plain identifiers enter
the query. A "Filter operations..." field before the select narrows both
groups and always keeps the chosen start.
The strings of the field moved to architecture/strings.ts. The "From"
select in SystemArchitecture is not rendered (Behavior returns the journey
before it) and is left as it was.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
At two layers of JSONBAgg the tooltip said "Likely past the render
limit", then that growing like the last layer adds about 402 nodes (90
plus 402 stays far below 5,000), and that loading stops at the render
limit of 5,000 nodes, while the layer then ended at 5,548 nodes.
- The warning names the limit and the number behind it: the 9,031 calls
the index counts at the 75 edge nodes, with the loaded count. The
growth estimate only stands where it is the larger number.
- The limit sentence says what loading does: it stops after the first
request that takes the layer past the node or edge limit, so the layer
can end above it, is marked partial, and the scene draws up to the
limit. The partial status and the blocked Expand say the same. The
Explore mini-Galaxy loads without limits and the hint claims none.
- The text moves from a native title (shown after the browser's delay)
to the Galaxy's own tooltip: at once on hover and keyboard focus, below
the button, also in its compact +1 form, closed on leave, blur and
Escape, with aria-describedby.
- Expand is blocked with aria-disabled instead of disabled, so pointer
and keyboard still reach the reason why it is blocked.
handtest-fixes-galaxy.mjs K8 reads the text from data-hint and matches
the new wording.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Hint protected every input and select of the page, also those inside a
closed <details>. Their content is not drawn, but the browser still gives
it a box, so the closed Path to and Edge types menus of the Galaxy
toolbar pushed the Expand +1 tooltip from its place below the button
into the page header.
Only drawn elements are protected now: checkVisibility where the browser
has it, and the content of a closed <details> (other than its summary)
everywhere. The readability probe applies the same rule.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
In the hierarchy a path through the mixed-directions band laid its edge
label onto the band title (DEFINES on "Mixed directions · 55 nodes" for
JSONBAgg <-DEFINES- general.py -DEFINES-> __init__): path labels only
avoided the path and root names, never the band title.
Every edge label of the scene now avoids one shared list of texts, the
path names, the root marker names and the band title. When the usual
spots along an edge are all covered (a wide title across a steep edge),
the label tries the points where it just leaves an obstacle before it
settles for the least overlap.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
"Refresh connections" read every route relationship again and nothing on
screen changed; Service map "Refresh", "Refresh analysis" and the
Behavior "Refresh" did the same. All four now share one control
(architecture/refresh-feedback.tsx): while the reading runs the button
reads "Refreshing …" and ignores a second press, afterwards a status
beside it names the time and whether anything changed ("Up to date at
13:29:43: no changes since the last load") or the error.
A refresh of the same index keeps the route connections on screen until
the new ones arrive instead of dropping back to "Reading …". The strings
live in architecture/strings.ts.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Architecture showed bare arrows at the right end of its subtab row while
Galaxy has "← Back", "Forward →" and "▾" at the start of its toolbar.
The controls now come first in the row with Galaxy's words and tooltips;
the row measures itself with the shared toolbar fit and shows the glyphs
alone once the tabs would scroll.
The Recent menus of both workspaces stayed open over the content. One
shared hook (graph/use-dismissible-menu.ts) closes them on a press, wheel
or focus outside, on Escape (which no longer reaches the page), on a chosen
entry and whenever the place changes.
The K27 browser check counts an in-view "← Back" outside the shared
history group only, since the shared one now carries the same words.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
drei hangs every Html label of the scene into the canvas wrapper and gives
it a z-index from its zIndexRange (up to 10, agent layer up to 80). The
wrapper formed no stacking context, so those numbers competed with the
Galaxy overlays: the path target's name was painted over Selection
details (z-index 4) and the fit view button (3).
The wrapper now isolates its stacking context. The labels keep their order
among themselves, stay above the canvas, and sit below every overlay with
a z-index of 1 or more, in the Galaxy and in the Explore mini-Galaxy.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Both opened a visible browser window that took the foreground. Like the
Galaxy and chat checks they now run headless; --headed shows the window.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Summary of the 28 hand-test findings, how they were checked on the final tree, and the known limits.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Date.now() around click() also counted Playwright's own wait for the
button and exceeded 600 ms under load, so the check measured the case
after the pause. The page now records the last input and the click that
reaches the button.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
A switch with the chat closed, with an answer running, while the old
project still loads, and a log line written after it; YAML lists of
mappings in the file facts; the opened-area card counting the parts the
view counts. The check that deletes the cached model and downloads it
again during a switch runs only with --with-download.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
A model download still running at a switch went to the dock of the new
project, but its progress stayed bound to the old dock, so the new
configuration showed an empty bar until the load ended. The progress of
a load now travels with the hand-over: the new dock shows how far the
download got and follows it from there.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
A project switch now stays in the page and only changes the address,
but the log buffer took the page once at start, so every line written
in cbm after a switch was filed under "/?project=django-demo". The
server stores one page per post. Each entry now keeps the address it
was recorded on, a batch ends where an entry of another page starts,
and a page that is going away sends the entries of each page it was on.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
The card of the opened django area said "Parts shown (18, largest
first)" while the view said "Browse map · 23 parts" and "23 parts · 100
connections": the 5 parts outside the area were left out of the count
and named only inside the connection list. The parts line now counts
what the view counts and names both groups: "Parts shown (23): 18 inside
the area, largest first: ...; 5 outside it: (root), docs, js_tests,
scripts, tests."
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
The keys of a list's items stand at the same indent as the keys of a
mapping, so the facts of django's .pre-commit-config.yaml read "repos
(10 keys: rev, hooks, rev, hooks, ...)" for a list of 5 items, under
"Read from the file; not generated by the model". A key that holds any
item is now a list: its items are counted and their keys are named as
item keys, "repos (list of 5; item keys: repo, rev, hooks)".
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
tools/handtest-fixes-galaxy.mjs now runs headless (Chromium channel, so
WebGL draws on the GPU; --headed shows the window) and adds:
- K5: at two layers around JSONBAgg no node the first incoming column calls
or defines (index truth via query_graph) stands in an incoming column
unless it also leads into one; outer outgoing nodes come from the first
outgoing column; the band below holds every mixed node, its heading counts
them and overlaps no name; every edge label carries a type and an arrow;
one layer has no band; Aggregate at two layers (651 nodes) keeps the names
of the root and its 34 direct neighbours with a visible note; call_command
(554 direct neighbours) keeps its own name and its 11 callees.
- K12: in Explore with .github/workflows/new_contributor_pr.yml open, the
mini-Galaxy has no overlapping label rects in the DOM with the chat closed
and open, and every root in view keeps its name.
- K13: "Lines 42 to 65 · end of file" and 65 as the last drawn line.
- K2: "← Back" and "Forward →" at the full level, the arrows alone at the
compact level, tooltips unchanged.
- K3: at the full level the root name is never squeezed below its text.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
K2 planned "← Back" and "Forward →"; the buttons showed only the arrows,
the words were in the accessible name and the tooltip. At the full toolbar
level the buttons now read "← Back" and "Forward →"; in the compact, tight
and wrapped levels they keep the arrows alone. Accessible names and
tooltips stay as they were.
Seen in the browser: with the words, the scoped toolbar kept the full level
with the chat open at 1600 px while the root name shrank to "JS…", because
nothing stuck out. The full level now also needs the root name whole
(data-fit-whole); a name cut only by its maximum width does not count.
With the chat open the toolbar then takes the compact level and the root
name stands in full.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Completeness check of K5: from two layers on every node stood on its
parent's side, so the far-left "incoming" column of JSONBAgg held the
callees of its tests (len, str, print) and the classes general.py defines,
next to real callers. The edge labels merged fans into "CALLS · TESTS ×8"
without a direction, and above 150 nodes no name was drawn at all.
- A node stands in an incoming or outgoing column only when the whole chain
from the root runs one way: left, each node points at its neighbour closer
to the root; right, the other way round. Nodes reached through both
directions (a callee of a caller, another caller of a callee) stand in a
band below the columns, centred under the root, inside a dashed frame
with the heading "Mixed directions · N nodes" on its top edge.
- Every line carries its own label with its types and an arrow from source
to target as the picture shows them ("CALLS · TESTS →",
"DEFINES_METHOD → · ← USAGE"); no fan is merged any more. A label stays on
its line or right beside it, avoids the band heading, and its measured
size follows its text, not the node pair, whose ids change with every
picture.
- Above 150 nodes the root and its direct neighbours keep their names, single
columns and edge labels. A root with more direct neighbours than that
keeps its own name and the side that fits (call_command: its 11 callees).
A note in the picture says who carries names and what brings the rest
back; the hint and the legend describe the band and are right for every
depth and trace direction.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Hand-test finding K12 (the mini-Galaxy part): with
.github/workflows/new_contributor_pr.yml open in Explore, the file scope
marks its six definitions as roots, and every root marker carried its name
fixed under its ring. In the 419 x 186 px panel "permissions",
"new_contributor_pr.yml", ".github/workflows/new_contributor_pr.yml" and
"jobs" lay on top of each other.
The same rule as for the edge labels of a path and of the hierarchy now
applies: a name stands at a free place or not at all. Every few frames each
root name takes the first place around its own ring (under, above, right,
left, one row further out) that overlaps no other name and stays inside the
canvas; among those, the one that covers the least of the other rings. A
name under or above its ring near the edge moves sideways into the canvas
while it still spans the ring. A name with no free place is hidden; the
hover card of its node still names it. A single root keeps its name under
the ring as before. The place is on the marker as data-name-slot.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
K3 asked for the acceptance check G7 to cover the open chat as well; it
measured only with the chat closed at 1600 px, where the toolbar never
wrapped. G7 now measures the scoped Galaxy toolbar with the chat closed and
then open at 1600 px (bar about 1170 px), counts rows by the centres of the
toolbar's own items, and passes only when both measurements are one row
without overflow. The chat is closed again for the checks that follow.
A small vitest guard reads the G7 section and keeps both measurements in it.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Completeness check of K13: at the end of django/contrib/postgres/aggregates/
general.py the pager read "Lines 42 to 66 · end of file" and drew an empty
line 66, while the file has 65 lines and the title of the disabled "Next
lines" already said "Line 65 is the last line of this file." The Module
range counts the empty segment after the final line break. On the last page
that segment is no longer drawn and the range ends at the real last line,
so range text, drawn lines and title agree. A file that ends without a final
line break keeps its last line.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Extends the chat stream script of the hand test (tools/handtest-fixes-
chat.mjs) with the points the completeness check of 2026-10-04 found
open:
- K24: django-demo with the agent active, a selection and a listed
answer, then cbm and django-demo through the project switcher, then
Back and Forward. Each leg measures that the page did not reload, no
model worker was created and nothing was prepared again, the lamp
reads "Agent active" at 0, 250, 1000 and 3000 ms, the workspace stays,
the chat shows the new project's history, and no request sent once
the address named the new project names the old one. After each
switcher leg all twelve views (Explore, Galaxy, Architecture Overview,
Entry points, Service map, Endpoints, Hotspots, System structure,
Behavior, ADR, Coverage, System logs) carry no marker of the old
project and are compared with a fresh page load of the new one. The
auto-load tooltip is read too.
- K16: three word orders with "wo" are listed, four misspelled names are
offered for the selection with the right wording; none reaches the
model.
- K12: the workflow's automatic card is facts only and offers "Ask the
model"; asking marks the model text as generated; tox.ini,
pyproject.toml and README.rst show facts only without a model call.
- K7: the django area opened with nothing selected lists its lines,
languages, parts, hotspot findings and connections.
- K1: an automatic explanation cut at 32 output tokens names both
limits.
The browser runs headless unless --headed is given; ANGLE on Metal gives
it the GPU with shader-f16 the local model needs.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Hand-test finding K1, minor point of the completeness check of
2026-10-04: the note under an automatic explanation that ran into its
token limit named only the output limits. It now names both limits of
automatic explanations and of a chat question: "Automatic explanations
stop after 128 output tokens so they stay short, and read at most 1,536
input tokens of source and facts. A question in the chat may read up to
2,048 input tokens and answer with up to 512 output tokens."
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Hand-test finding K7, Architecture part (completeness check of
2026-10-04): an area opened in the Overview with nothing selected inside
it (the hand test opened "django" at 17:55) was explained by a single
fact, "Opened: django", because the view published only the path.
SpatialArchitecture now publishes what it shows of an opened area, file
or hotspot area: its measure, the parts shown with their files and
indexed lines, its hotspot findings and the connections of its parts,
with the areas outside it named so. The chat lists them as facts: the
opened area, indexed lines and languages, "Parts shown (N, largest
first)", the hotspot findings and "Connections of its parts". Without
source the facts are the explanation and the model is not asked, as for
a selected area.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Hand-test finding K12, prose part (completeness check of 2026-10-04): the
automatic Explore card of the open workflow still added model text that
went beyond the file ("allows us to interact with GitHub repositories
directly from the command line"); the names-not-in check only catches
identifiers.
The automatic card of a configuration, data or text file (YAML, JSON,
TOML, INI and its relatives, .env, properties, Markdown,
reStructuredText, XML, ignore and attribute files) is now the facts read
from the file and nothing else, marked "Read from the file; not generated
by the model." The model writes about the file only on request: "Ask the
model" on the card (its text stays marked as generated and is left out
when it names what the file does not show) or a question in the chat
(with the names-not-in note as before).
browser-ai/file-facts.ts reads the facts without a parser library: the
kind and length of the file; a workflow's name, triggers, jobs and
actions as before; the top-level keys of YAML and JSON with what they
hold; TOML tables and INI sections with their key counts; the title,
sections and code blocks of Markdown; patterns of ignore files; the root
element of XML. A marked part is named as that part, a cut excerpt only
by its kind and the note on what was left out. The same facts go into the
prompt of a question about the file.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Hand-test finding K16 (completeness check of 2026-10-04): two kinds of
uncertain caller questions still went straight to the model.
A typo in the selected name ("wer ruft JSONBAg auf", "wer ruft jsonbgg
auf?", "who calls jsonbag", "callers of jsonbag") was recognized as a
caller question, rejected as a question about another symbol and never
offered. A subject up to two edits from the selection's name (one for
names of four to six letters, none below), in any case, now gets the
suggestion with the listed answer one click away, and the reply says that
the name, not the question, is what is uncertain. An exact name of another
symbol in the scope stays that symbol.
"jsonbagg wird wo aufgerufen" took "wo" for the subject. Question words
(wo, woher, wohin, wann, where, when) are never a subject now, and the
name may come first ("JSONBAgg wird wo überall aufgerufen", "... wird
aufgerufen von wem", "jsonbagg is called by whom"). The words between name
and verb may carry umlauts.
Table-driven tests cover 26 word orders and 21 misspellings in German and
English and the questions that must still go elsewhere.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Hand-test finding K24 (completeness check of 2026-10-04): a project switch
still reloaded the whole page (window.location.assign). The worker with the
loaded model went with it and the lamp showed "Agent loading" for about
3 s while the next page loaded the model again from the cache.
A switch now stays in the page (app/project-windows.tsx): the address
changes with pushState, Back and Forward switch the same way (popstate),
and each project gets a fresh window, the same tree under a new key. Every
panel starts from fresh state as after a reload, so no workspace can show
the old project, and the window's RPC and /api requests carry a signal
that aborts once the new window is mounted. The window hands its
successor only what it reports: the workspace in view, whether the chat
is open, and the agent's lamp and model name.
The chat dock of the old project hands its worker to the dock of the new
one (browser-ai/agent-handover.ts) inside the switch, before the new
window renders, so its first frame is already loaded: no new worker, no
second load, no "off" or "loading" frame. A running answer is stopped and
settles before the new project uses the model; a model still loading
finishes loading there. A model no dock takes is unloaded when the switch
ends, and an unmount outside a switch unloads as before. The sessionStorage
resume note (agent-resume.ts) is gone. The first project of a window is no
switch for the dock, so it keeps a model it was handed while the project
resolves.
The auto-load tooltip now says what happens: a switch keeps the model in
the page, the option only matters when the page is opened or reloaded.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
The K27 browser run now also checks that System structure and Behavior show
no Back of their own, that Plan or 3D in System structure and Behavior is a
step Back restores, that the Behavior path and the operation on the call
chain come back after Back and Forward, and that text typed in the Routes
filter survives an immediate Back. Screenshots reset every scrolled
container so each one shows the subtabs and the history controls.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
Review follow-up for K27:
- The opened Overview area or file reaches only Overview, and the hotspot
area only Hotspots. Endpoints, Entry points and Hotspots no longer draw
or report an area opened in Overview, so the chat context names nothing
that is not on screen.
- Plan or 3D in System structure and in Behavior is part of the entry and a
step, as on the shared map. The Behavior path to a destination is a step;
the operation on the call chain and the page of direct calls stay with the
entry without being steps, so Back and Forward return to them.
- The in-view "Back" buttons of System structure and Behavior are absorbed
into the one Back beside the subtabs.
- Back, Forward or a Recent jump while typing in the Routes filter first
records the typed text as a step instead of dropping it.
- replaceNavigation also merges with the step after it, so no two equal
steps end up next to each other.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
tools/handtest-fixes-k27.mjs drives the hand-test flow on django-demo in a
headless browser: Overview, open django, Routes, Endpoints, open the /edit
route group, System structure, expand a group, Behavior, start handle in
loaddata.py, follow a call. It then goes back step by step to the start and
forward again, checking at every step the subtab, location trail, route
perspective and filter, system focus and expanded groups, Behavior start
and the Back and Forward tooltips. It also checks Alt+Left and Alt+Right
(and that they do nothing while typing in the filter), a jump from Recent,
that a new navigation after Back drops Forward, that Alt+Left in Galaxy
leaves the Architecture history alone, and that switching to cbm starts a
fresh history. Every step writes a screenshot and index.md.
Signed-off-by: Bernhard Jackiewicz <bj@techport.io>