Skip to content

History

Revisions

  • Mobile: Knowledge round 3 (#1002) + three widget-wrangling techniques Module page gains a "Round 3" section: the editor footer reaching the bottom edge, and full-screen text editing. Three things promoted into Techniques: - A JS-mounted widget injects its stylesheet at RUNTIME, so it beats yours on source order whatever the specificity. TinyMCE's skin lands after mobile.css; .tox-* rules need !important. The tell is a fix that looks like it HALF worked -- hiding the upsell badge succeeded while flex-wrap on the menubar did nothing, which read as "mostly fixed" until it was measured. Measure which half. - Reclaim a third-party widget's own chrome before adding your own. Four wrapped menubar rows plus an upsell badge were ~130px of a 720px screen; scrolling the menubar rather than hiding it returned 78px of typing area with every menu still reachable. - Build your own full screen, don't borrow the widget's. TinyMCE's fullscreen plugin was one line away but had no reachable exit in this init -- a phone user could get stuck. A fixed overlay with your own Close plus a pushState back-button exit cannot trap anyone.

    @edmozley edmozley committed Aug 9, 2026
    c1e2e44
  • Mobile: Knowledge round 2 (#1001) + three more techniques Module page gains a "Round 2" section: Back onto the action row, the Gmail-style collapsible meta block, and the sticky peek-through fix. Three things promoted into Techniques so the next module inherits them: - The sticky peek-through has now happened in TWO unrelated modules. A sticky element sticks to the top of its scroll container's CONTENT box, below its padding-top. Check the container's padding first; move the space onto something that scrolls away rather than deleting it; make any negative margin match the card's padding exactly. - A collapsible summary should BE the tap target, not sit beside a chevron -- bigger target, and its accessible name is the visible text, so it needs no aria-label and therefore no new string in 24 languages. Draw the chevron with borders; a glyph renders as a dot at 11px. - Harvest a translated word rather than writing one. common.back took each locale's own existing translation from change-management.php -- no EN-only key, no silent fallback, and the generic word lands in common where the next module finds it.

    @edmozley edmozley committed Aug 9, 2026
    da08cf1
  • Mobile: Knowledge (#1000) + three lessons for the next module New page Mobile-Friendly-Knowledge. Linked from the overview (status table, honest assessment, layer count), the sidebar, all three sibling module pages, the Techniques reference table and Knowledge.md. Three things added to Techniques so the next module inherits them: - A wide table that "fits" hasn't been fixed, it has been crushed. display:block + overflow-x is only half the rule; without nowrap on the cells the browser wraps every column to one word per line, which passes the overflow measurement and is unreadable. - Grep a module for localStorage before bringing it along. Two of four modules have carried a saved DESKTOP mode that re-applies itself on a phone (tickets #762, knowledge editor pop-out #1000). Invisible in the markup; breaks for one user and not the rest. - The Share dropdown is a second instance of re-positioning a JS-positioned element in pure CSS. Also recorded on the module page: `let` globals are not window properties (so a harness cannot read them from outside), and a desktop-width overflow on a page that never opted into mobile.css is the control, not the bug.

    @edmozley edmozley committed Aug 9, 2026
    1f2815a
  • Mobile: Calendar (#998) + the two lessons it produced New page Mobile-Friendly-Calendar. Linked from the overview (status table, honest assessment, layer count), the sidebar, both sibling module pages, the Techniques reference table and Calendar.md. Two things worth carrying forward, both written up: - "docScrollW === innerWidth proves the page won't reflow to desktop. It does not prove the page is usable." The settings page passed every measurement while still being a four-column table you had to drag sideways -- the container's overflow-y:auto had quietly made it a horizontal scroller, so the table genuinely WAS contained. - A rule that "already covers this" is worth measuring. LAYER 3's 16px anti-zoom rule had never applied to any typed input, losing on specificity to inbox.css, app-wide, since the tickets rollout. Also recorded: every opted-in page must declare its own scroller (LAYER 2 makes body a 100dvh flex column and flex items shrink by default), and getComputedStyle beats getBoundingClientRect for anything inside a container that animates in with a transform.

    @edmozley edmozley committed Aug 9, 2026
    8448c17
  • Adding a language: what Bahasa Melayu taught, and who the script is actually for Four additions, all from doing ms (24th locale) rather than from theory. WHO THE SCRIPT IS FOR. The page reproduced a Claude Code workflow as though it were the process. It is not: Workflow/agent/pipeline are harness APIs, so a contributor without Claude Code could not run a line of it, and it carried the maintainer's own machine path. Now split explicitly -- the PROMPT and the VERIFICATION are the reusable artefacts and work for a human translator, another tool or an agency; the orchestration is one maintainer's tooling. Paths generalised. MODEL GUIDANCE. Run the agents on Sonnet, not Opus: the failure modes that matter are instruction-following, and step 3 catches every one mechanically, so the larger model buys quality where it cannot show. ms was done this way with zero structural faults. And use ONE model for the whole locale -- mixing them risks an inconsistent register within a single language, which is worse than either alone. TICKETS IS TOO BIG FOR ONE AGENT. At ~1,550 keys a single agent spent twenty minutes WITHOUT EVER ATTEMPTING A WRITE -- it read the source, then went grepping its sibling locale files to see how "Save" and "Delete" had already been translated, then started a task list about terminology. Not a crash, not a hang, not a truncated file: the transcript kept growing, and the tell was writes=0 after twenty minutes while its siblings finished in two. The fix is three changes, all needed: split by top-level section into six chunks that write fragments; HAND the agent lang/<code>/common.php as the only consistency reference so there is nothing to go looking for; and tell it plainly not to research, naming the failure. CHECK 5 — SIBLING-LANGUAGE DRIFT. New, and no structural check can ever catch it: a translator drifts between closely related languages while the file parses, every key is present and every placeholder survives. Applies to ms/id, nb/nn, and any Iberian or Serbo-Croatian variants added later. The defence is a word list used twice -- as a "use this NOT that" table in the prompt, and as a grep afterwards. For ms/id the decisive marker is bisa vs boleh: ms came out 432 boleh, zero bisa. Also records banning false friends outright -- "kereta" is car in Malay and train in Indonesian, the class of error that produces confident nonsense. Plus two smaller ones in step 4: update the README language count (a number in prose, so nothing reports it stale), and register the locale LAST on a live install, because a file mid-write is unparseable and a registered locale with an unparseable file is a fatal rather than a fallback.

    @edmozley edmozley committed Aug 8, 2026
    893daba
  • Search: the user page and developer guide for what actually shipped The design page was carrying the whole feature on its own, which conflates two different readers: someone who wants to search their tickets, and someone who has to maintain the thing. Searching-Inside-Tickets — how to use it. The new field, what a result means, the three query forms, and honestly what it does NOT do: attachments are not searched, and nothing indexes new tickets until the backfill is run again. Also the two one-off setup steps, and why D007 is worth running ONCE at setup rather than only when something looks broken -- because database search fails quietly, and a "longest word" limit makes words like authentication unfindable while shorter ones work perfectly. Search-Corpus-Developer-Guide — the traps, in the order they will bite: - one corpus rather than an index per table, because relevance scores from different full-text indexes are not comparable - tenant_scope, because NULL means OPPOSITE things in the sources - the predicate goes INTO the query, and why post-filtering starves results worst for the least privileged user - query translation is not pass-through: requiring a term below the index minimum makes the WHOLE query match nothing - InnoDB not exposing uncommitted rows to MATCH, and the three ways that surfaces - the three server settings that break search silently, and that WAMP ships one of them wrong in [wampmysqld64], not [mysqld] The design page now points at both and says plainly that it is the reasoning rather than the manual -- its lasting value is the ruled-out section, which is the part nobody can reconstruct from the code. Every file named in the guide verified to exist; every wiki link verified to resolve. Both pages added to the sidebar under the command palette.

    @edmozley edmozley committed Aug 8, 2026
    e3498aa
  • Full-text search: the page said "nothing here is built" and that is no longer true Phase 1 is part built -- searching inside ticket messages and notes shipped as #991-#996 -- so the opening line had become the exact failure this wiki warns about elsewhere: a design document quietly describing a product that has moved on. Replaces the status block with two tables: what shipped, and what has NOT. The second matters more, because "full-text search works" would be read as including attachments, and attachment text is the entire hard half that does not exist yet. Indexing also does not happen as tickets arrive -- the backfill is run by hand. Keeps the pre-build warning but sharpens it: where the page describes something now built, the code is the authority. Its lasting value is the ruled-out section, because rejected options and their reasons are the one thing nobody can reconstruct by reading the code.

    @edmozley edmozley committed Aug 8, 2026
    53739d9
  • Database Verification guide: the index list carries a TYPE, not a boolean Pairs with freeitsm #991, which taught the index backfill about FULLTEXT so the forthcoming search corpus table can reach grown installs and not just fresh ones. The guide said the backfill "only covers KEY / UNIQUE KEY / INDEX", which is now wrong, and it did not describe the row format at all -- so anyone reading the generated list would reasonably have assumed the old boolean. Documents the format change and, more importantly, the trap: the old idiom was $isUnique ? 'UNIQUE KEY' : 'KEY' and the string 'key' is truthy, so code reading the element directly would now build a UNIQUE index over duplicate-laden columns. Callers use dbVerifyIndexTypeOf(). Also records why a wrong index kind is worse than a missing one -- a FULLTEXT index downgraded to an ordinary KEY keeps the count correct while every full-text search on an upgraded install silently returns nothing -- plus the two full-text specifics worth knowing: InnoDB only indexes CHAR/VARCHAR/TEXT, and the first full-text index on a table rebuilds it (harmless here, since db_verify indexes a table it has just created empty).

    @edmozley edmozley committed Aug 8, 2026
    2c028e6
  • Full-text search: how it's configured, and how many engines to support Two additions from working through the admin-facing side. 8.4 WHERE THIS IS CONFIGURED. Not a card per engine under System -> Integrations, for two reasons. Search is singular where trackers are plural -- Jira and Azure DevOps can both be connected because a ticket can escalate to either, but only one thing answers a search query, so per-engine cards would describe a model that does not exist. And a search engine is not an integration in that sense at all: integrations are external systems you hold an account with and talk to as a peer, whereas an engine is infrastructure, the same family as the database or the mail server. The right precedent is already in the product -- the mailbox modal, where you pick Microsoft/Google/IMAP and the provider-specific fields appear. One Search screen, two independent sections (extraction and backend, per 8.0), plus index status and a rebuild. Explicitly install-wide and admin-only: two analysts cannot be on different engines because there is one shared index, so this is the same class of setting as the database connection rather than a per-analyst preference. 8.5 HOW MANY ENGINES. An earlier draft said support exactly one. That was too conservative and ignored this codebase's own precedent: AI providers and issue trackers both run multiple providers behind one contract, and adding Azure DevOps as the second tracker needed no schema or core change. The surface here is only four operations, so an adapter is plausibly 150-250 lines with no client library, and OpenSearch forking from Elasticsearch 7.10 means one adapter likely serves both. There is also a real-world argument: "we already run Elasticsearch" is common, and making that admin add a second search system cuts against the no-lock-in position. The genuine cost is not code volume. EVERY ADAPTER IS A NEW PLACE AUTHORISATION CAN FAIL OPEN, because each translates the permission predicate into a different filter syntax, and a translation bug returns rows rather than raising an error -- a wrong search result looks exactly like a right one. That makes search adapters sharper than AI adapters, where wrong output is visibly wrong. Conclusion: design the interface for many, ship one, add on demand. The first adapter is what proves the contract; building three against an untested interface means rewriting three. And every adapter must pass the same permission test suite -- fixed "this user must see exactly these ids" cases answered identically by every backend -- which is what makes admin choice safe rather than merely generous.

    @edmozley edmozley committed Aug 8, 2026
    b4a36a2
  • Full-text search: state exactly what it will and won't match Answers the most concrete question anyone can ask of a search feature: if a document says "Tower of London", what must you type to find it? Both the natural queries work -- "tower London" and "tower in London" -- but NOT because of fuzzy logic. Word order is irrelevant because the index holds individual words, and "in" is an InnoDB stopword dropped from query and index alike. Those two behaviours cover a lot of everyday searching and are easily mistaken for fuzziness. What MySQL genuinely cannot do is forgive a misspelling or a word ending. No edit-distance matching, and -- the one that will bite hardest -- NO STEMMING, so "printers" does not match "printer". People type both constantly. Postgres has a stemmer; MySQL does not. The trailing-wildcard mitigation (+printer*) is noted along with its cost: over-matching on short stems, and no leading wildcards. This also supplies the one honest argument for a phase 2 engine that is not about scale. Typo tolerance is the headline feature of Meilisearch and Manticore. Staying on MySQL trades forgiveness rather than speed, and that is better known now than discovered from a user asking why a search with one transposed letter found nothing. Cross-referenced from section 10, distinguishing this (a definite, knowable gap) from the genuine uncertainties listed there.

    @edmozley edmozley committed Aug 8, 2026
    cea5307
  • Full-text search: separate the two optional services explicitly Feedback that "optional external search" was the one unclear part, from someone who had been through the entire design conversation -- so it will certainly be unclear to a reader arriving cold. The cause is that the plan contains TWO unrelated optional services and the page never said so plainly: an EXTRACTOR (reads PDFs, turning them into text -- the only route to PDF search, so likely wanted) and a SEARCH ENGINE (searches faster at enormous scale -- rarely needed). The expected shape for an install wanting the full feature is extractor and no engine. New 8.0 states that up front, with the three properties of the engine option that matter more than which engine it is: off by default and a setting rather than a version; no data moves, since the engine holds only a derived copy while the corpus stays the source of truth; and switching it off loses nothing.

    @edmozley edmozley committed Aug 8, 2026
    caccfa2
  • Full-text search: filtering by source, attribution, and three MySQL limits Prompted by asking whether the corpus can search JUST notes or JUST attachment names, and whether a result can say which part of a ticket it matched. It can -- source_type is an ordinary column, so per-type filtering, facet counts and "found in a note on ABC-123-45678" all come for free. But answering it properly surfaced four things the design had left implicit. TICKET SUBJECT BECOMES ITS OWN ROW (source_type 'ticket'). Buried as the title of the first email, "matched the subject" cannot be stated cleanly to the user or weighted separately from body text. THE RESULT SHAPE IS TWO BOUNDED QUERIES, not one clever one: rank tickets with GROUP BY + LIMIT, then fetch the matching documents for just those ticket ids. That yields "matched in: subject, 2 notes, 1 attachment". Doing it the other way round -- fetch documents, collapse afterwards -- reintroduces a top-N distortion, because the top 200 documents may collapse to only a handful of tickets. THREE MYSQL CONSTRAINTS now stated explicitly, because each would otherwise be found during implementation: - MATCH() must correspond exactly to a FULLTEXT index, so searching filenames alone needs a SECOND index on (title). Added to the schema. - Filenames may be better served by LIKE than by full-text at all: they tokenise badly and people search fragments, which is what LIKE is good at and full-text is weak at. The two searches can use different mechanisms. - There is no field weighting inside a MySQL full-text index, so ranking a subject hit above a body hit means composing the score by hand. None of these change the shape of the design -- the corpus still works -- but all three are the difference between it being a data model and being a usable search experience.

    @edmozley edmozley committed Aug 8, 2026
    0d799c0
  • Full-text search: add the corpus design, and answer #53 point by point Two additions, both prompted by the question "can this actually cope with searching ordinary rows AND extracted document text?" NEW SECTION 4 -- the corpus. The page previously said "index the bodies and notes" and "extract attachment text" without saying how those become ONE searchable thing. The naive build (a FULLTEXT index per table, then UNION) does not work: relevance scores from different full-text indexes are computed against their own corpus statistics, so a note hit cannot be meaningfully sorted against an attachment hit, pagination has nothing coherent to page by, and every new source is another branch that must re-express the permission rules. So: one search_documents table where a row is one searchable unit whatever its origin -- email body, note, attachment text, knowledge article. One index, one relevance scale, one query. The schema and the query are both on the page. Two properties fall out of it: the permission predicate sits in the WHERE alongside the match, so pre-filtering is the path of least resistance rather than something to remember; and a GROUP BY collapses hits to their ticket, because users think in tickets and one ticket with the term in four replies would otherwise flood the first page. The duplication objection is answered on the page: stripped plaintext is needed anyway because body_content is HTML, so derived text is stored either way -- the only question is one table or three. NEW SECTION 5 -- does this answer the discussion? Their three questions answered directly, then their requested content types and file formats mapped to what the design does with each. Two honest results rather than a clean sweep: their request for "historical conversations" depends on the unmade backfill decision, and PDF -- the format they listed first -- is the one needing an optional extra service, with scanned PDFs needing OCR on top. Also notes that their third question named KNOWLEDGE ARTICLES, which the earlier draft had missed. They fit the corpus as another source_type, but Knowledge already has semantic search, so that is two search paths over one body of content and the UI must be clear which is which. NEW SECTION 10 -- what we are not confident about, kept separate from the design because confidence is not uniform: MySQL relevance quality is its weakest area, the three-character token minimum may bite hard in a product where people search short codes, and volume behaviour is untested. With the cheap way to settle all three first -- build the corpus and the search function only, load real data, run real queries, before any UI or extraction work is committed. Sections renumbered accordingly; all cross-references verified to resolve.

    @edmozley edmozley committed Aug 8, 2026
    5ab19db
  • Blue sky: full-text search across tickets and attachments Records where the design conversation from discussion #53 landed, including the ideas ruled out -- which is the part that otherwise gets re-derived. The governing insight is that extraction is the hard half and indexing is not. MySQL 8.0 already does full-text natively; turning a PDF into text is the genuinely difficult problem. So the useful third-party integration is a document parser, not a search engine. Covers what phase 1 would be (ticket bodies and notes first, attachments in optional tiers), the security rule that the QUERY must carry the permission controls rather than results being filtered afterwards, the settings path for very large installs, and the anti-brick-wall rules that keep the backend swappable. Ruled out, with reasons: vectors as the foundation (they sit downstream of extraction and don't remove it, cosine-in-PHP doesn't scale, semantic search is weakest at serial numbers and error codes, and embedding every attachment ships customer documents to a third party on a self-hosted product); an external search engine as the STARTING point (still extracts nothing, adds a sync problem MySQL doesn't have); post-filtering results; and any hard PDF dependency. Two corrections to how the request was framed, marked on the page: the extracted text must NOT be one of the things a settings screen relocates -- it stays in the FreeITSM database and is the source of truth the index is rebuilt from, which is what stops an engine change becoming a re-extraction project. And the conversation had drifted attachment-heavy: ticket bodies and notes are the bulk of the value and the cheapest part. Also adds what the brief left out: whether history gets backfilled, whether deletion and GDPR erasure cascade to the index, that snippets are themselves content, and that extracted text is a plaintext copy carrying the database's protections rather than the file's. Listed on Blue-Sky-Thinking and in the sidebar.

    @edmozley edmozley committed Aug 8, 2026
    270a76a
  • Add the 'Adding a Language' developer guide, and refresh the locale list The workflow script that translated all 23 modules into pt-BR lived in a session temp directory and was deleted with that session, so the next language had to start by rewriting it from memory. The script is now reproduced in full, along with the two non-obvious decisions in it (no StructuredOutput, because it failed silently on files over ~350 keys; pt-BR as a structural template only) and the four verification checks — the placeholder one especially, since a key can pass both the count and the set check and still be broken. Internationalisation: the locale table said 20 and had never listed uk. Now 23, with nb and nn added.

    @edmozley edmozley committed Aug 7, 2026
    bf8960c
  • Slack dev guide: fix a heading that had drifted from its own section

    @edmozley edmozley committed Aug 7, 2026
    283657d
  • Split Slack into a user page and a developer guide Slack.md was a hybrid. It is now the user-facing page — what it does, setting it up, the two traps that produce a working-looking result, the health check, and how it behaves. Slack-Developer-Guide.md takes the architecture: why it is not tracker #3, the bring-your-own-app reasoning, the kind-in-the-registry seam and its known debt, the file map, the four design decisions, and the six traps that only a live run exposed. Both validated: every wiki link resolves and every backticked path exists. Promoted eight bare filenames to full paths — the same thing that reads like a path but is not, caught by the same check as last time.

    @edmozley edmozley committed Aug 7, 2026
    3ee72e7
  • Add Slack; cross-link from Issue Trackers and the sidebar Slack as a messaging channel (freeitsm #977), proven end to end on a real workspace. Covers why it is NOT a third issue tracker, the bring-your-own-app decision and what it costs, the two setup traps that produce silently degraded results rather than failures, every scope with its justification, the file map, and the four bugs the live run found that the test suite could not.

    @edmozley edmozley committed Aug 7, 2026
    e8d0a78
  • Basic IMAP: the PHP 8.4 extension removal is not a Windows problem (issue #50) chris18890 is right that the 8.4 section was framed as Windows-only. ext/imap was unbundled from PHP core in 8.4.0, so it hits every OS. Corrected with the verified per-distro picture rather than a blanket warning: Ubuntu ships the PECL rebuild as php8.4-imap in universe from 25.10, so apt still works there. Debian 13 is the one with no clean answer — php-imap is in neither trixie nor trixie-backports, only experimental — and 'apt install php-imap', which this page told people to run, fails outright. Also states the ZTS caveat we had been quietly contradicting: php.net says the extension should not be used with thread-safe builds, and Apache on Windows is always thread-safe.

    @edmozley edmozley committed Aug 6, 2026
    bff4b7a
  • Add Help Page House Style; link from Theming and the sidebar The rule behind assets/css/help.css, now that all 26 in-app guides use it (freeitsm 2df4f17): the level 1/2/3 heading model and why the indent lives on the parent, the four accent tokens a page declares, the "colour that carries meaning stays" test, the full component table, a starter template, and the traps the sweep turned up — class names living inside lang/ strings, section numbers typed into headings, .help-table being a wrapper not a table class.

    @edmozley edmozley committed Aug 6, 2026
    b3fc55e
  • Blue sky: CRM and a real service catalogue A parked-ideas page in the established shape — honest about the catch, not just the upside. Grounded in the actual schema rather than written as a wishlist, and the research turned up two facts that became the spine of the argument: 1. FreeITSM knows far more about the companies it BUYS FROM than the ones it SERVES. `suppliers` has 19 columns (legal name, VAT, reg number, address, due-diligence dates); `tenants` has 6. `contacts` records a supplier contact's job title, mobile, direct dial and switchboard; `users` records a customer contact's theme preference and no phone number at all. Every contract in the system hangs off supplier_id — there is no customer-side agreement anywhere. 2. There are ALREADY THREE unrelated ideas of "a service" — CMDB objects of the Service class, the status page's hand-kept status_services, and portal-visible forms. None know about each other. So a catalogue's first job is reconciling those three, not becoming a fourth. The page argues the useful version is not a sales CRM (no pipeline, no campaigns, not the system of record for a sales team) but the account layer an ITSM tool is uniquely placed to hold, and ranks the ideas by value over effort: - cost-to-serve, which is buildable almost entirely from data already captured — ticket_time_entries.time_spent_minutes exists; the only missing input is an analyst cost rate - renewal warning, where contracts already has end dates, notice periods and a contract.expiring workflow trigger, just pointed at suppliers - account health from evidence (SLA breaches, CSAT, reopen rate), with the rule that every number must click through to its tickets, and NO single composite score - entitlement, which is what makes a service catalogue earn its place - feeding the CMDB blast radius a commercial question Avoid-list includes the ones that would actually bite: two sources of truth for "company" when tenants is a security boundary; sales users inside an analyst RBAC model; personal data arriving with no retention answer; a portal that reads like a storefront; and cost-to-serve pointed at individuals, which would just stop people logging time. Left explicitly undecided: whether a customer contract is a flag on contracts or a separate entity, and whether an "account" is a tenant or something below it — the second changes the schema, which is the main reason this is parked rather than scheduled. Every schema claim on the page was re-verified against the live database after writing. Indexed on Blue-Sky-Thinking and added to the sidebar; all eight outbound links resolve.

    @edmozley edmozley committed Aug 5, 2026
    7eca5ab
  • CMDB: document the rebuilt object detail page The v2 layout is now object.php, so the user page and the two developer guides that described the old one are updated. CMDB.md — the detail-page section is rewritten: - header: class icon, the signal colour taken from the item's own data, and the chips that are also the controls (state, parent) - the four headline numbers, and what each one means - Details: cards for filled fields, empty ones collapsed - Connections: the merged panel, with a table of the four KINDS and how each one is created, and why the tally counts by kind rather than by direction - "Adding a relationship — in either direction", with the worked preview sentence — this is new capability, not just a new layout, so it gets its own section - blast radius reframed as a left-to-right chain - Impact Panel / Inline Mini-Graph sections removed; a note explains what they were merged into and why Developer guides: - foundations: the detail-page file-table row, and a new warning under "two kinds of link" that a relationship ROW is directional and the UI can now write it either way — no direction column, no second row, the inverse still comes from inverse_verb at render time. Anything creating a relationship must decide which end is the subject rather than assuming it is the current object. - impact guide: renderBlastRadius() -> blastHtml(), and a note that cmdbDirectImpact()'s buckets are now consumed by connectionsHtml() / connectionTally() instead of being rendered as three panels. Three i18n lessons added to the foundations guide's verification section, all earned this session: - parity is NOT enough — audit placeholders too. {depth} was added to blast_headline_other in EN only and parity passed perfectly. - keys built by concatenation are invisible to a grep. - prove there are no unresolved keys from the RENDERED page, since a missing key falls back to its own name. Checked: every pathed file reference still resolves, all three internal anchors resolve, no object2 references remain anywhere.

    @edmozley edmozley committed Aug 5, 2026
    9cbd983
  • CMDB: correct drift in the three developer guides and the module page Small factual fixes found while auditing docs/cmdb.md against the code: - foundations guide claimed "three" AI features and listed two. The third, "suggest a relationship", only ever existed in the design doc. Says so now, with a warning to check api/cmdb/ before trusting that doc. - file table was missing create_impact_diagram.php (#973), the three AI settings endpoints, cmdb/help.php, and the ticket_cmdb_objects table. - known gaps now list the tree view and the unbuilt AI feature. - impact guide s11 said the graph visualisation was still V2 "and this ships as a grouped list" - contradicting s10 on the same page, which documents the Network Mapper handoff that answers it. - lang key counts had drifted (582 and 547 against an actual 586). The foundations guide carries the total; the impact guide no longer repeats a number that has to be maintained in two places. - CMDB.md said the help guide has 12 sections; it has 14. - CMDB.md now frames docs/cmdb.md as pre-build design rationale rather than "the full design and roadmap", and lists ticket_cmdb_objects. Pairs with #974 in the app repo.

    @edmozley edmozley committed Aug 5, 2026
    666c94e
  • CMDB: document the blast-radius diagram handoff Records why this is a handoff to Network Mapper rather than a second graph renderer, and the layout/connector/provenance rules.

    @edmozley edmozley committed Aug 4, 2026
    aeb6aa8
  • CMDB: foundations + data-quality developer guides Clears the documentation debt. The module had no developer guide at all despite being the most complex one; it now has three, scoped per feature like the rest of the wiki. The foundations guide covers the thing most likely to be got wrong: there are two entirely separate kinds of link (containment means ontological dependency and cascades; relationships do not), plus where the shared write rules live and what is deliberately outside them, and why delete is explicit rather than trusting FK cascades that a Verify-grown install does not have.

    @edmozley edmozley committed Aug 4, 2026
    4667a4c
  • CMDB: document the data-quality audit User page section covering the six checks, why they are framed around impact analysis, and that findings are advisory rather than a score to get to zero.

    @edmozley edmozley committed Aug 4, 2026
    e6192fc
  • CMDB: impact analysis developer guide + blast radius on the user page The module had no developer guide at all despite being the most complex one, so this adds the first: how the blast radius is computed, why most links are deliberately ignored, and the two traps (direction is not a boolean; a function_exists guard on the company filter fails open). Scoped per feature to match the other guides. A foundations guide covering the data model, class system and tenancy invariant is still owed.

    @edmozley edmozley committed Aug 4, 2026
    fd3eb91
  • Point the Updates links at updates.php The wiki still linked to the retired updates.html on every page via the sidebar, plus once on Home. That stale page is what search engines and LLM crawlers were finding and quoting, so the project looked like it had not shipped anything since 30 May 2026.

    @edmozley edmozley committed Aug 4, 2026
    d98f639
  • Issue trackers: workflow rules work the same for both (#967) Ed asked whether the workflow module can drive Azure DevOps like it does Jira. It can — you pick a CONNECTION, so the tracker follows from that and nothing about writing the rule differs. The page now says so, since "raise it in Jira" implied otherwise. Added, because they are the two ways a rule can be written and still be wrong: - leave Project and Issue type BLANK and the connection's mapping decides, which sidesteps the next point entirely; - ⚠️ if you fill them in they mean different things per tracker — Jira wants the key, Azure DevOps the name — and Azure DevOps issue types come from the project's template, so a rule hard-coded to Bug works against an Agile project and fails against a Basic one in the SAME organisation. Also noted that the tracker triggers can branch on which tracker fired them, so per-tracker rules are possible rather than one rule having to serve both. Neutralised the last two Jira-shaped paths in shared sections (the mapping screen's location, the dry-run note). Verified no dead in-page anchors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    @edmozley edmozley committed Aug 2, 2026
    c400994
  • Issue trackers: make the analyst page genuinely cover both (#964) Ed asked "what should the project key be if I send it to devops" — and that question was the review. The page defined project key as "the KAN in KAN-6", which is not merely Jira-flavoured wording but ACTIVELY WRONG for Azure DevOps, where there is no key at all and the project NAME is the identifier. The connector page shipped with the connector; this is the analyst half catching up properly rather than having a DevOps preamble bolted onto a Jira walkthrough. - "Which project?" is now a two-row table, because the answer genuinely differs: Jira wants the KEY, Azure DevOps wants the NAME, and getting it wrong produces a permission-shaped error that reads like bad credentials. - "Which issue type?" gains the per-PROCESS table. ⚠️ Two projects in ONE Azure DevOps organisation can disagree about whether Bug exists — Basic has no Bug at all — so "raise it as a Bug" is not a safe default the way it reads on Jira. Also documents that a blank type becomes a Task, because Task is the one type in every process. - The connection table is now Jira | Azure DevOps side by side, which is where the missing email field and the extra Resolved setting become obvious rather than needing prose. - Neutralised the shared prose that quietly assumed Jira: the scheduled check, the multi-company rule, the never-blocks-the-ticket note and the comments section all describe behaviour that is not Jira's. Kept Jira-specific what genuinely is — the Atlassian token steps are scoped with a marker rather than generalised into uselessness. Verified: no dead in-page anchors after renaming "Comments from Jira", no dead page links. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    @edmozley edmozley committed Aug 2, 2026
    749d0be