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.
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.
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.
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.
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.
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.
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.
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.
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.
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>
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>
749d0be
Issue trackers: Azure DevOps connector, and the contract test it settled (#964-966)
New page: Azure-DevOps-Connector-Developer-Guide, the sibling of the Jira
one. Issue-Trackers gains Azure DevOps setup and the Resolved setting;
the two guides that PREDICTED this work now record how it turned out.
The engine guide committed to a falsifiable claim before Jira shipped:
adding connector #2 must touch nothing but the provider, one registry
row and its settings partial. It held, and the page now says so with the
three things that did change for everyone:
- settings_fields, and why it is separate from credential_fields — a
dropdown among the credential boxes inherits their blanking rule and
silently resets on every save;
- integrationsAbsoluteUrl(), because the ticket link every connector
puts in an issue was built from BASE_URL, which is a path, so it 404'd
from inside any tracker and had never worked;
- $target may be keyed either way — core's neutral `issue_type` or the
provider's own word — because reading only one silently creates the
wrong kind of work item.
The Jira guide's "if you are writing the next connector" list is now
marked as validated, with two of its decisions reclassified as Jira-ONLY
now there is something to compare against:
- relative JQL dates exist because Jira reads an absolute date in the
USER's timezone. Azure DevOps honours an explicit UTC Z. Copying
either across is a bug, and both are right for their own tracker;
- flavour-dependent author_identity is an Atlassian quirk, not a general
requirement. What IS general is that it match testConnection().
What the new page records that the code cannot say for itself:
- ⚠️ which field holds the body depends on the work item TYPE. A Bug's
form shows Repro Steps and not System.Description, so writing the
description to the obvious field returns 200 and produces a work item
that looks empty to the developer who opens it;
- 🔑 five state categories, not four, and why the fifth is a per-connection
SETTING rather than a mapping — plus the live vindication of "never
branch on a status name": "Resolved" is category Resolved on a Bug and
InProgress on a User Story, same project;
- ⚠️ timePrecision=true is a query parameter, not a body field, and the
tempting fix for the error it throws silently re-reads a day of comments;
- ⚠️ echo suppression must use createdBy.id — the two endpoints report
DIFFERENT descriptor formats for the same person, so comparing those
never matches and every comment we write comes back as a developer's;
- ⚠️ attachments are two steps and step one attaches the file to nothing;
- ⚠️ a rejected token answers 203 with a sign-in page, not 401 with JSON.
Also corrected two stale rows in the analyst page's "not there yet"
table: attachments and "tell the requester when it is done" both shipped
weeks ago and were still listed as missing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
a43d94e
Forms: lookup fields — analyst page + developer guide (#962/#963)
New page: Forms-Lookup-Fields-Developer-Guide. The security surface
earned it its own guide rather than another section in the sections &
conditional-logic one — three gates, a scoping rule whose NULL means the
opposite of Knowledge's, and a bug that only showed up under a live run.
Forms.md gains a Lookup fields section (and the type count moves from
nine to ten, which the page had been getting right by hand).
What the dev guide records that the code cannot say for itself:
- Why one type with a source, not one per source. Same argument as the
date field's mode, and it will come up again for the next type:
field_type cannot change once a field exists.
- The registry IS the SQL whitelist — those column names are
interpolated, so they may only ever come from the constant. Four of
five column sets assumed while building were wrong, and a wrong column
name renders as "no matches" rather than an error, so a source can be
broken and look merely empty.
- Why contracts and software are absent, with the specific schema gap
each is waiting on.
- The three meanings of $tenantIds, since null / [] / [ids] are all
legitimate and confusing two of them is silent.
- ⚠️ NULL tenant means "the Default company's" here, the opposite of
Knowledge — and on a single-company install every row is NULL, so the
clause handling it is not an edge case, it is the feature working.
- The #963 post-mortem, because the generalisable part is not the fix:
a function_exists() guard around a security rule degrades silently,
and not necessarily toward the safer behaviour. Here it degraded
toward refusing legitimate answers.
- That adding a field type means five places, one of which is PROSE —
and that the prompt had already drifted, omitting datetime and section
for months without anything failing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4799f0b
Issue trackers: sending an attachment after escalation (#961)
Records why it is a button rather than automatic, and the guards on an
endpoint that takes two ids from the browser — chiefly that the
attachment must belong to that ticket, or any file in the system could
be posted to any issue.
546ac3c
Issue trackers: attachments proven live, and refresh the stale header
A real 83 KB PDF went from a ticket to KAN-6 and Jira reported it back
byte-identical, so 7g is no longer 'unproven live'.
The page header had drifted two releases behind: it still said tracker.*
triggers did not exist and quoted 252 assertions. Now #945-960, 288
assertions, 12/17 parity, with what is genuinely still missing listed.
3d8805b
Issue trackers: attachments (#960)
Gains 7g: why inline images are excluded (and what that costs), the
size/count rules, why a failed upload must never fail the escalation, and
the three Jira specifics that make an upload work — the no-check token, the
field named `file`, and a random boundary.
Also records a test lesson: asserting the bytes AROUND a sanitised
filename fails on correct output. Assert the value.
3a44bad
Time-Based Triggers: these had never actually fired (2026-08-02)
The page described the design correctly; the code did not match it. A
duplicate sla_format_minutes() fataled the SLA cron on invocation, so
sla.warning and sla.breached had never fired since the feature shipped
on 17 May — with two further wrong-column bugs hidden behind it.
Recorded here because the page is what someone will trust when adding
the next one: a trigger being registered, documented and visible in the
editor proves nothing about whether it fires. Make it fire.
8a2d9f8
Issue trackers: tracker.* triggers (#958), and why they are not time-based
Engine guide gains 7f: the three triggers, that each carries the ticket,
the aliased columns in the payload query that are silent when wrong, and
the comment loop that terminates only because send_note_to_tracker
records what it pushes.
Time-Based Triggers gains a "a cron is not the same as time-based"
section, because reusing the emissions ledger for these is the obvious
instinct and it is wrong — a fingerprint on current state would swallow
a genuine third transition.
8445e85
Issue trackers: separate the two "no change to the tickets module" claims
Escalation genuinely added nothing to the tickets module and that still
holds. The module as a whole stopped being untouched at #954, because
reading comments back means rendering them — get_notes.php, inbox.js,
inbox.css and tickets/index.php are all in it now.
Worth pinning because the stale version is exactly the sort of line that
ends up in published copy.
14378a5
Issue trackers: the pretty-URL relative-path trap on the help page
Three traps worth naming, all hit within minutes of adding the page: a
pretty URL deeper than its file breaks relative asset paths (use
BASE_URL); $path_prefix must stay filesystem-relative because the system
header requires through it; and a System page must use the system
header, not the root one, or it renders the ticketing nav.
71222c0
Issue trackers: the grouped routing rules and the in-app setup guide
Records why departments and companies are never one flat list (the
grouping is what makes precedence visible), and documents help.php —
including that its rewrite must precede the provider rule.
29b767e
Issue trackers: document mapping (#957)
Engine guide gains 7e: the precedence rule and why it is that way round,
why priority has no wildcard, the narrow rejected-priority retry, the
namespaced local_key, and two traps — the column is ticket_type_id not
type_id, and a shared service must not assume it owns the transaction.
Analyst page gains a Mapping section in plain terms, and the Jira page
records that mapping needed no connector change beyond listPriorities(),
which is the abstraction doing its job.
c5aee9b
Add a Scheduled tasks page; bring the tracker dev guides current
New page: Scheduled-Tasks. Ed asked for one place covering every cron
job / Windows task FreeITSM needs, because "you must schedule something
or comments never arrive" was buried in the Jira page. Covers all four
jobs, what silently breaks without each, Windows and Linux commands, how
to run one by hand, and how to tell whether they are working.
Also documents what is NOT a scheduled task, so nobody goes looking for
a job that does not exist or schedules the InTune workers, which the app
starts itself. Notes that mail is fetched by the browser, so nothing is
collected overnight with every browser closed.
Windows gotchas that cost time today: /ru SYSTEM tasks are invisible to
a non-elevated session, 0x41303 means "not yet run", and the escaped
quoting fails if a path has spaces.
Linked from Home, the sidebar, and five places in Issue-Trackers.
Dev guides brought current for #954-956: assertion count 227, range
#945-956, the pipeline diagram no longer shows the removed author
guard, and a new note on inbound_enabled — a column that shipped in V1
with no writer, so the feature it gated could never be turned on. That
is a trap worth naming for the other columns V1 shipped early.
06e4b2b
Issue trackers: why author-based echo suppression was removed (#956)
Records the failure and the reasoning, at length, because the idea is
tempting enough that someone will reach for it again: "authored by our
account, therefore our own echo" assumes a service account that never
types, and the token owner is usually a human who comments.
Also notes the uncomfortable part — the suite was green and contained
assertions pinning the wrong behaviour, because they shared the design's
assumption. Only a live account found it.
fb7f808
Issue trackers: say what "the check" is, and where the tickbox lives
Both raised by Ed while setting this up for real, and both were fair.
"Run the poll" was jargon that meant nothing to a user. The page now
explains the check in plain terms — FreeITSM asks Jira rather than Jira
calling us — says that nothing on the page works until it runs, and
shows how to run it once by hand for testing.
The Accept updates tickbox is inside the connection, not on the list
page, so the page now spells out the pencil-then-scroll route, notes the
token box can stay blank, and adds both to troubleshooting.
dbece7d
Issue trackers: author_kind, and why "Unknown" was the wrong answer (#955)
Records the four cases a note's author can be in, and that collapsing
them into one label discards information the row holds. Also notes that
delete_analyst.php reassigns nothing, so a departed analyst's notes are
the normal case rather than an edge case.
c027ddb
Issue trackers: comments coming back, and the join that hid notes (#954)
The engine guide gains 7d — the inbound pipeline, both echo guards, the
"first poll imports nothing" rule and why the comment map row is written
before the note (the unique key is what wins a race between two cron
runs, not the check before it).
The Jira guide gains 7: two calls rather than one per issue, why the JQL
date must be relative (an absolute one is read in the Jira user's
timezone, not UTC), why the ADF reader lives in the connector rather
than IssueDoc, and that author_identity must match testConnection() or
echo suppression silently stops working.
The analyst page documents the toggle, that imported comments are always
internal, and that enabling it does not bring back a backlog.
Also recorded: get_notes.php inner-joined notes to their author, so 10
of 17 notes on the dev install were invisible. Both dev pages now list
what forces an update, including the rules above.
de457a2
Tracker guide: correct the shipped range, point at the parity scoreboard
4603699
Issue trackers: the analyst page, and cross-links (#945-953)
The dev pair existed but the analyst page did not, so a user had no
page at all. Issue-Trackers covers setup (including the API token
living in the Atlassian account, not Jira), raising an issue, reading
the pill, automating it with a workflow rule, and an honest 'what is
not there yet' — comments coming back is named as the biggest gap
rather than left to be discovered.
Also: sidebar entry, and both dev pages now point at it.
ebe7f6a
Jira guide: the removed search endpoint, and real-Jira proof (#953)
Cloud retired /rest/api/3/search for /search/jql; DC keeps the old one.
Recorded with the lesson that generalises — a connector's correctness
depends on someone else's API, so when a call starts failing, suspect
the endpoint before the logic.
Status banner now records that the whole journey has run against a live
Jira Cloud site.
6aa2531