v0.8.0
If you keep an overrides file, read this before upgrading. A file
containing wireless = "false", hide = "false", a fractional port, or a
misspelled key used to render; it now stops the run with an error naming the
problem. Those files were never doing what they said, which is why this changed,
but the failure is new and it is at the point of use. The fix in every case is
in the error message.
Changed
-
The documentation is split into
docs/, and links into the README have
moved. It had reached 1233 lines, which is past the point anybody reads,
and is now 220: what the tool is, what it produces, how to install it, and
how to see a map without touching your network. Everything else is a page of
its own, indexed from the README.If you linked to a README anchor, it has moved. Every
#sectionthat is
now a page is a different address:Was Now README.md#usage,#reading-the-diagram,#flag-referencedocs/usage.mdREADME.md#credentials,#unifi_api_keydocs/credentials.mdREADME.md#mapping-from-a-support-filedocs/support-files.mdREADME.md#json-for-programs,#mermaid-for-documentationdocs/output.mdREADME.md#sharing-a-map---obfuscatedocs/sharing.mdREADME.md#artwork-licensing-and-attributiondocs/artwork.mdREADME.md#how-it-works,#caveatsdocs/verification.mdREADME.md#manual-overridesdocs/overrides.md, which already existedPages are organised by why somebody opens them rather than by what they are
about, and each was then checked against what its own opening sentence
promises. That found the artwork page describing where pictures come from with
the answer two files away, the output page covering two formats of seven, and
three pages repeating their own title as their first section. All were the
same artefact: splitting on top-level headings moves text correctly and lands
it by accident.The README's
## Manual overridesand## Also plannedsections are gone
rather than moved, becausedocs/overrides.mdandTODO.mdalready held the
same material and the README was carrying second copies to drift against.The guards were widened before the split rather than after. Every link used to
be a same-file#anchor; most are nowdocs/artwork.md#something, which can
fail two ways a browser renders happily: the file may be missing, or present
without the heading. The link check now resolves cross-file targets, the flag
and command checks read every document rather than the README alone, and the
generated flag reference lives indocs/usage.md. It caught fourteen links
the split broke silently. -
The documentation says which UniFi applications this has been run against,
in the introduction rather than buried: Network for everything, and one
Protect endpoint read purely to tell a camera from an Access reader. Devices
from Access, Talk or a UNAS already draw, since they are clients or UniFi
hardware like anything else, so the gap is narrower than "unsupported": what
is missing is the second source that would let an ambiguous match resolve.
CONTRIBUTING.mdasks for those environments alongside the other things
nobody here has. -
Two released sections described one change twice. 0.7.0 had two entries for
unifi-map shape, written days apart; 0.6.0 had two forRELEASING.mdthat
contradicted each other, one saying a fix had been claimed and never made and
the other making that same claim. Both are merged. A test now fails when a
release describes the same subject twice, with genuinely separate changes to
one thing listed as exceptions rather than the rule loosened. -
The artwork page lists user-supplied artwork among its sources. An
iconin
an overrides file is where a picture comes from when none of Ubiquiti's
catalogues has one, and it is the only source that works under--offline. -
Both generated references were wrong in the same two ways, and neither
staleness check could have noticed.docs/usage.mdand the man page each
printed a synopsis reading{fetch,render,all}, two commands behind, and the
man page listed only those three underCOMMANDS. Neither said that
unifi-map overridesrequires acheckargument, because the introspection
walkedoption_stringsand a positional has none. All three lists are now
derived from the parser.The two existing checks regenerate the file and fail on a diff, which catches
an author who forgot to runmake docsand cannot catch a generator holding a
hardcoded list: it produces the same wrong file every time and compares equal
to itself forever. A separate test now asserts that both documents name every
subcommand and every positional. A document claiming it cannot drift from
--helpis worse than a hand-written one when it does, so the claim is tested
rather than trusted. -
overrides checknow validates against the same topologyrenderwill
build. It passedinclude_offline=Trueunconditionally, which is more
permissive than the default render, so a selector naming a device the
controller merely remembers passed the check and then failed the render it had
just been checked for. That is the one outcome the command exists to prevent.
--show-offlineis now shared between the two subcommands rather than
belonging torenderalone. -
An override that displaces a link the controller reported now says so.
[[link]]and[[hosted]]both detach a node from its current parent before
attaching the stated one, which is necessary and, for[[hosted]], the entire
point: reparenting a VM under its hypervisor displaces a real observation by
design. But the code assumed the displaced edge was always the "uplink not
reported" placeholder, and nothing enforced that, so contradicting the
controller was silent. It warns now, naming both ends. Tidying the placeholder
stays quiet, since warning on the documented case is how a warning stops being
read. -
The runtime hint about unplaceable clients pointed at "Manual overrides in the
README", a section the documentation split moved todocs/overrides.md. -
The man page omitted exit code 3 (Graphviz not installed), and its
description listed neither Mermaid nor JSON among the output formats. -
Several documented behaviours did not match the code, all found by an external
review reading the split documentation against the source. Each is a fix to
the document except where noted:docs/output.mdsaid nothing is ever written to stdout;unifi-map shape
writes its report there, which is what makes it pipeable.- It listed
dotamong the formats needing Graphviz.dot,mermaidand
jsonare all written directly and work without it; onlysvg,pdfand
pngneed it. Verified by rendering with an emptyPATH. - Its JSON example named version 0.6.0 and omitted
titleandnetworks,
the latter promised two paragraphs above it. - Its Mermaid example claimed to be the shipped demo while showing a direction
and header no documented command produced. The example is now byte-identical
to--layout treeoutput, with the one edit stated. docs/overrides.mdcalled[[device]].kindrequired; it defaults to
unknown. It also never documented whatnotedoes, which differs per
block: an edge label for[[link]]and[[hosted]], nothing at all for
[[device]]and[[node]]. And it did not mentionoverrides check.docs/usage.mdsaidfetchdownloads the icon font only when missing; it
replaces any cached copy every time.docs/support-files.mdsaid omitting--fetch-fingerprintsleaves clients
without product artwork. The flag governs the download, not the lookup: a
database already cached is read either way.docs/verification.mdsaid "all five output formats" when there are seven,
and ended with aBEGIN GENERATED FLAGSmarker that had noENDand no
content, left behind when the split moved the flag reference to
docs/usage.md..env.examplepointed at a Credentials section of the README that is now
docs/credentials.md.- The README said Access readers, Talk phones and a UNAS "all still appear".
docs/verification.mdcorrectly calls the UNAS case inference, none of the
three having been seen here, and the README now matches it.
-
Support-file operational guidance was on the artwork page. Site selection,
the four archive limits, the compression-bomb defence and the slow-walk
warning all sat inside a section about client icons, while
docs/support-files.mdcovered none of them. Moved, and site selection and
limits now come before the artwork asides, since a multi-site archive stops
the run before artwork is reached. -
CLAUDE.mdproposed the JSON export,overrides checkand the Mermaid export
as future work, all three having shipped, and still described removing the
sanelayout alias in 0.6.0, which 0.6.0 did. The entries are rewritten to
keep the constraints that still bind rather than deleted. The issue template
for feature requests pointed contributors at that file's planned-work section,
naming two shipped features; it points atTODO.md, which exists precisely
becauseCLAUDE.mdis written for agents. -
Two defects in the override-displacement warning added earlier in this same
unreleased cycle, both found by an external review before either reached a
release:- It leaked under
--obfuscate. Overrides are applied before obfuscation, so
an ordinary obfuscated render logged the node's real label, its old parent's
real label and the selector, contradicting the promise that log output is
scrubbed too. The scrubbed diagram was the whole point, and a terminal
beside it naming the nodes defeats it. Displacements are now carried out on
ApplyResultfor the caller to report, exactly as hidden nodes already
were, so the policy sits with the code that knows about the flag. Under
--obfuscatethe warning survives as a count with no names. - It could call an asserted link controller-reported. Only the "uplink not
reported" placeholder was excluded, so when two overrides reparented the
same node, the second warning described the first override's own link as
something the controller had reported: the exact misattribution the warning
exists to prevent.
- It leaked under
-
The documentation no longer counts its own external reviews.
README.md,
SECURITY.mdandAI_DISCLOSURE.mdeach claimed a specific number of
independent AI reviews, plus a total number of findings and a per-review
breakdown. Reviews happen whenever a substantial change lands, so every one of
those numbers was stale within days of being written and three files had to be
edited in step to keep them honest. They now describe the practice: security,
documentation, code and architectural review, more than one of each, by more
than one system. Nothing else about the claim changed, including that every
pass so far has found something its predecessors missed and that one finding
stands declined with its reason recorded.While removing them,
AI_DISCLOSURE.mdsaid two findings were declined and
later done anyway whereSECURITY.mdandCLAUDE.mdboth say one. Corrected
to one, and it now names which. -
The overrides guide said the displaced-link warning names both ends. It does,
except under--obfuscate, where the fix above deliberately reduces it to a
count. The exception is now stated where the guarantee is. -
--support-max-archivewas the one cap whose error did not name the flag that
raises it, which is the worst of the four to omit it from, since it is the one
a legitimately large site is most likely to hit. Now tested for all four. -
Smaller documentation corrections: the support-file page called its four
adjustable limits "three" twice; the overrides page said three of the four
block types acceptnotewhen all four do; the JSON example said everything
butnodesandedgeswas complete while abridgingnetworkstoo; the
README said Graphviz was required outright, whendot,mermaidandjson
need nothing installed; and three cross-references still pointed at README
sections the split had moved (CLAUDE.md, the--icon-fonthelp text, and
the pull request checklist). -
A misspelled section name was ignored entirely. Keys inside a block were
checked and the block names were not, so[[lnik]]parsed, matched nothing,
and the run reported "applies cleanly" with zero links: a file whose every
line was ignored, reported as a success. Writing[link]instead of
[[link]]is refused too, since TOML accepts it as a table and this file
wants a list of them. -
An SVG icon took its size from whatever was drawn inside it. Dimensions
were read from the first 4 KiB rather than from the<svg>element, so a
64x32 drawing containing<rect width="7" height="5"/>measured as 7x5 and
rendered at a twelfth of its size. -
An overrides file could mean the opposite of what it said.
wireless = "false"andhide = "false"both read as true, becausebool("false")is
true and TOML has real booleans that are easy to quote by accident.port = truebecame port 1, sinceboolsubclassesint.port = 1.9became port- And a misspelled key such as
wirlesswas accepted and ignored, so the
link simply stayed solid with nothing said. All four now stop the run, and the
unknown-key error lists what the block does accept. This is the rule the
feature already claimed: a stale or mistyped override fails loudly rather than
quietly doing something else.
This will stop a run that used to work, for anyone whose file contains one
of those. That is the point rather than a side effect: a map drawn from a file
meaning the opposite of what it says is worse than a run that stops and says
where. Every message names the block, the key and what was expected. - And a misspelled key such as
-
SVG artwork was accepted and then broke the render. Graphviz refuses an
SVG with no XML declaration, reporting it as a file that "was not found",
which fails the whole run; measuring it here and letting it through turned a
bad icon into a bad map. The accepted subset is now what Graphviz will
actually load, verified by rendering one rather than by reading a spec, and
width="."no longer raises whilewidth="0.5"no longer becomes a 0x0 icon
drawn as nothing. -
Downloaded artwork bypassed the pixel cap. The guard was added to the path
that measures files already on disk and not to the one that decodes bytes off
the network, which is the path that matters most. -
An icon whose path contains
&was left in the output as a path. Graphviz
writes it into an XML attribute, so it arrives as&and never matched
the permitted set, defeating the inlining that exists to keep local paths out
of a file meant for sharing. -
unifi-map shapecould print the values it promises never to print.
Unrecognised keys were named, filtered to "schema-shaped" tokens on the
reasoning that a field name is controller schema worth seeing on an unfamiliar
version. That filter accepted10.0.0.5,nas,secretssidand
branch-office, because a short lowercase token is exactly what an address, a
hostname, an SSID and a site name look like, so a payload keyed by any of them
was reproduced under a heading stating that could not happen. Unrecognised
keys are now counted and never named. That loses the discovery of new field
names, which was half the reason to run it elsewhere; a document claiming to
be publishable has to be publishable first. -
--obfuscateleft real network names and ids in the JSON export. Aliases
were built from the networks nodes referenced, so a configured network with no
active clients was missed and kept its real name, and every network kept its
real controller id regardless. The leakage test excluded JSON and Mermaid
despite being named for every output format; it now covers both, and the
fixture carries an unused network with an identifying name. -
A fresh install could not fetch the hardware catalogue at all.
_fetch
returns a smallFetchedobject rather than arequests.Response, and the
catalogue loader called.json()on it, which does not exist. The resulting
AttributeErrorwas not caught by the surrounding handler, so the very first
render on a machine with an empty cache and a reachable CDN crashed. Every
test either seeded the cache or simulated a network failure, so the ordinary
success path was the one thing never exercised; it is now. -
--obfuscateerased the difference between an asserted link and an observed
one. Edges are rebuilt field by field during obfuscation andassertedwas
not carried, so a link stated in an overrides file came out drawn exactly like
one the controller reported. That distinction is the project's central promise
and--obfuscateis the mode where a reader is least able to check it. A test
now walks the dataclass, so a field added toEdgeorNetworkfails until it
is either carried through obfuscation or deliberately exempted. -
A cached snapshot could mix two fetches.
write()only wrote the payloads
it had whileread()loads every recognised file present, so an endpoint that
succeeded once and failed later left its old file to be read beside fresh
data. Switching one cache directory between a live fetch and a support file
did the same. A snapshot is now a complete generation: recognised files absent
from the new fetch are removed, and nothing else in the directory is touched. -
The diagram's own subtitle counted the wrong things. It was computed
before overrides were applied, so a map that declared devices or hid nodes
stated the pre-override totals underneath itself. -
An override could not be trusted to catch every loop. The cycle check kept
one parent per node and followed only that, which is exact for the graphs this
tool builds today but is a property of its callers rather than of the check. A
cycle reachable only through a second parent went undetected. Now a full
depth-first search, iterative so a malformed graph cannot exhaust the stack. -
A user-supplied SVG or JPEG icon left an absolute path in the output. The
SVG post-pass inlined only.png, so other formats stayed as filesystem
references, disclosing a local path (usually containing a username) in a file
whose purpose is to be shared. draw.io export had the matching bug from the
other side, labelling every iconimage/pngwhatever it was. -
The image size cap was not a cap. Pillow warns at
MAX_IMAGE_PIXELSand
only raises at roughly twice it, so an image up to double the limit decoded
anyway, and neither exception it raises derives from those being caught. The
guard is now scoped to each operation withcatch_warningsrather than
changing the whole process's warning filters, and covers_measure, which
every cached and user-supplied image passes through. -
SVG artwork in an overrides file never worked, though it is documented as
working: measuring went through Pillow, which does not decode SVG, so every
SVG was refused before Graphviz saw it. Dimensions are now read from the file,
by regex rather than an XML parser, since this is a file somebody else may
have written. An SVG with only aviewBoxis still refused, because Graphviz
ignores those silently and a named error beats a blank node. -
Mermaid identifiers deleted punctuation rather than replacing it, so
asserted-a-bandasserted-abbecame the same node. Labels and titles now
also survive a newline, which previously ended the statement carrying them and
let the rest of a device name be read as Mermaid source. -
unifi-map shapecounted configured networks as "client networks", including
ones no client is on, and reported every field of the topology graph as absent
because that payload is a single object rather than a list of records. -
Smaller: a size argument that overflows a float is now a clean error rather
than a traceback,--support-max-entriesrefuses zero and negatives, private
directories are created private rather than tightened a moment later, and
Networkcarriesis_guest, which the JSON export had been trying to read
through agetattrthat could never succeed.
Added
-
Nine device icons, drawn by this project rather than fetched. Ubiquiti's
artwork covers their hardware and the clients their fingerprint database
recognises; everything else fell through to a bare Graphviz primitive, so an
access point was a trapezium and an unplaceable client was a diamond.Five are infrastructure, keyed on the device's role. Four are clients, split on
guest and wireless, which is the same four-way split the console's own icon
font encodes. Those four close a gap with no other answer: that font is
served only by a controller and is absent from a support file, so mapping an
archive without touching a console left unidentified clients as shapes. It no
longer does, which removes the last reason a support-file user needs a
controller.They appear in two places.
--icons builtinno longer means "no artwork"; it
means artwork that is ours, the Internet cloud included, and produces a
complete map with no network access at all. And inside--icons unifithey
are the fallback for hardware absent from Ubiquiti's catalogue, which is a
small, deliberate step away from "unifishows exactly what the console
shows": on the shipped demo it changes exactly one node. Devices the catalogue
covers are untouched.One defect found before any of this shipped, recorded because it is the kind
that hides:render_dotdiscarded the resolved icons entirely unless the icon
set wasunifi, a leftover from whenbuiltinmeant no artwork existed. The
icons drew correctly and never reached the map. The same gate had been
silently dropping icons supplied through an overrides file, which are not
fetched from anywhere either, so that is fixed too.Nine places across the README, four pages under
docs/andSECURITY.mdstill
promised "plain shapes" or "geometric shapes" for--icons builtinand for
unidentified clients. All now describe what happens. Two related corrections
came out of the same sweep: the artwork page presented one precedence order
when the implementation has three (infrastructure, clients and the Internet
node resolve by different code, and the ISP brand mark was missing from the
table entirely), and the transparency note saidbuiltinnodes keep a filled
background of their own, which stopped being true when the fallback shapes
became transparent PNGs.Drawn with Pillow, which was already a hard dependency, so nothing new is
required and nothing is vendored. The silhouette carries the meaning rather
than the colour, with guest drawn hollow rather than in a second hue, so the
set survives greyscale and colourblind readers. Aspect ratios are real, so a
switch is wide and short and a handset is taller than it is wide. Each theme
colour caches separately. -
The shipped demo marks its guest client as one.
is_guestis a separate fact
from sitting on the guest VLAN, and without it two of the nine new icons never
appeared in the demo at all. -
Every page under
docs/links back to the documentation index. A search
result or a shared link lands a reader on one page of a set, with nothing on
it saying that a set exists or where it is listed. Suggested by an external
documentation review, and guarded, because the convention is the sort that
holds across eight files and quietly lapses on the ninth. -
Three documentation guards, each for a class of drift the existing checks
could not see. The JSON example is parsed and its version and top-level keys
compared against real output; the Mermaid example is diffed byte-for-byte
against what its stated command produces; and every document is checked for a
generated-content marker without its pair. All three were mutation-tested by
reintroducing the exact defect they were written for.