Repository navigation
Projects Phase Save Did Nothing
Fixed in 3.5.1 Β· Reported in #175 by @LvNixia Β· Present in 3.3.0, 3.4.0 and 3.5.0 Β· Module: Projects
On a project's Plan tab, + Add a phase (or editing an existing phase) opened the dialog, but Save did nothing. Pressing Enter did nothing either. The dialog stayed open and nothing was saved.
The cause was two dialogs on the same page giving their parts the same names. When the page's code asked for "the Save button", it got the Save button from the stakeholder dialog instead, so the phase dialog's own Save button was never wired up.
While fixing it, a second clash of the same kind turned up: Announce disruption on a project's Connections tab ignored the title you typed and answered "Give the maintenance a title."
Both are fixed. Nothing wrong was ever saved: the server refused every bad request these bugs sent.
The reporter diagnosed this exactly, down to the element ids and the fix. That made it a quick fix.
- Plan β + Add a phase (or the pencil on an existing phase): fill it in, press Save - nothing. No error, no request, the dialog stays open. Enter in the name box: the same.
- Connections β Announce disruption: type a title, press Save - "Give the maintenance a title.", however many times you tried.
- People β a stakeholder's Place / Edit: this one did save. But each Save also sent a second request, for a nameless phase, which the server refused. You would not normally have noticed.
Every part of a web page that the code needs to find again gets a name, called its id. The rule is that an id is used once per page. The code then asks the page for "the element called psSave" and gets exactly one thing back.
The project page holds many dialogs, all present in the page from the start and shown when needed. Two pairs of them broke the rule:
| Dialog | Its part | Clashed with |
|---|---|---|
| Stakeholder (People tab) | the header name, the error line, the Save button: psName, psError, psSave
|
the phase dialog's name box, error line and Save button, with the same three ids |
| Ask AI panel | its header: paTitle
|
the Announce disruption dialog's title box, also paTitle
|
When two things share an id, the browser does not complain. It quietly returns whichever comes first in the page. The stakeholder dialog comes before the phase dialog, and the Ask AI panel comes before the announce dialog, so the newer dialog lost both times.
document.getElementById() returns the first element in document order with that id. Duplicates are invalid HTML, but nothing reports them: no console warning, no failed lookup.
In projects/view.php the stakeholder modal (#prjStakeModal) is rendered before the stage modal (#prjStageModal). Two scripts each wired "their" Save button at start-up:
// assets/js/projects-tools.js
document.getElementById('psSave').addEventListener('click', saveStake);
// assets/js/projects-view.js
document.getElementById('psSave').addEventListener('click', saveStage);
document.getElementById('psName').addEventListener('keydown', e => { if (e.key === 'Enter') { ... saveStage(); } });Both lookups found the stakeholder button. So:
- the stage modal's Save button had no listener at all, and Enter was bound to a
<span>that never receives keystrokes; - the stakeholder Save ran
saveStakeandsaveStage.saveStagereadpsName- now the stakeholder header<span>, whose.valueisundefined- and posted a stage with an empty name toapi/projects/stage_save.php.ProjectsService::saveStage()refuses a blank name ("Give it a name."), so nothing was created.
The announce clash is less obvious, because it looks like it works. The Ask AI panel's header is a <div id="paTitle">. openAnnounce() ran getElementById('paTitle').value = '', which on a <div> doesn't throw: it just adds a plain JavaScript property called value to the div. saveAnnounce() then read that property back, '', and posted an empty title. The real input, further down the page, was never read. statusPlannedSave() refused the empty title with "Give the maintenance a title.", which was what you saw.
The first diagnosis of the announce bug here was wrong. Reading the code, it looked like
.valueon a<div>would beundefinedand.trim()would throw. Running the page showed it was'', becauseopenAnnounce()had set that property moments earlier. The symptom in the table above comes from the run, not from reading the code.
The issue named the phase dialog. Checking every id on the rendered page turned up the second clash, on the Ask AI panel, which came in with 3.3.0's Ask AI. Both are now fixed, and the page has no repeated ids.
The same check over every PHP page in the codebase found three more files with a repeated id. None of them is a real bug:
| Page | Id | Why it is fine |
|---|---|---|
auth/login.php |
localLoginForm, username, password
|
the two copies are in opposite branches of an if/else; only one is ever sent |
self-service/tickets.php |
tkFilter |
the same: one for the personal view, one for the team view |
reporting/intune/index.php |
drillCount |
the second is JavaScript rebuilding the heading's contents, replacing the first |
The reporter's suggestion, taken as written, plus the second clash:
-
Stakeholder dialog: every id renamed from
ps*topstk*(pstkName,pstkPower,pstkInterest,pstkStance,pstkKeep,pstkError,pstkSave), inprojects/view.phpandassets/js/projects-tools.js. Only three ids clashed, but the whole set was renamed, so the stakeholder dialog has its own prefix and the next phase field cannot clash with it. -
Ask AI panel header:
paTitleβpaHeading, with itsaria-labelledby. No script looked it up by id, so that was the only change. - The phase and announce dialogs keep their ids, so
assets/js/projects-view.jsdid not change.
| File | Change | |
|---|---|---|
| π₯ | projects/view.php |
stakeholder modal ids ps* β pstk*; Ask AI header paTitle β paHeading; assets/js/projects-tools.js cache-buster bumped to v12 |
| π¦ | assets/js/projects-tools.js |
openStake(), saveStake() and the Save listener use the pstk* ids |
The real project page (an active demo project with stakeholders and announcements switched on) was opened in headless Chrome, signed in as an administrator. Every save request was intercepted and answered with a stub error, so nothing was written to the database. The test then clicked through each dialog the way a person would and recorded which requests left the page.
| Old code (control) | Fixed code | |
|---|---|---|
| Repeated ids on the page |
psName, psError, psSave, paTitle
|
none |
| Phase dialog: Save | no request | one api/projects/stage_save.php, with the typed name |
| Phase dialog: Enter in the name | no request | one api/projects/stage_save.php, with the typed name |
| Stakeholder: Save |
api/projects/tools.php member update plus a api/projects/stage_save.php with an empty name |
api/projects/tools.php member update only |
| Announce: Save |
api/projects/tools.php announce with "title":""
|
api/projects/tools.php announce with the typed title |
The left-hand column is the control: the same test, run against the code as it was before the fix, reproduces every symptom.
Not tested: a real save going all the way to the database (the requests were stubbed on purpose, because the test ran against real data). The request bodies are exactly what the unchanged server code already accepts, and nothing on the server changed. Not checked on a phone, where these dialogs are the same markup.
Update to 3.5.1 and hard-refresh the browser (Ctrl-F5) so the new assets/js/projects-tools.js loads. Phases, stakeholders and disruption announcements then save normally. There is nothing to repair: no phase, stakeholder or announcement was saved wrongly while the bug was live.
Related: Projects Β· Projects - Developer Guide Β· Bugs resolved
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
-
Architecture
- β³ π§Ή Root folder tidy (URL routing)
- β³ π Database Integrity (FKs & constraints)
- β³ π οΈ Database Verification β Developer Guide
- β³ π§© Service Layer (UI + API share code)
- β³ πͺ€ Code traps (the TRAP: comments)
- β³ π Commenting for the Engineering wiki
- β³ π οΈ Engineering wiki scanner - Developer Guide
- β³ π Service-Layer progress
- π§ͺ Developer tests
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- π Date & Time Formats
- π± Currencies
- π Display order (modules, Watchtower cards) β Developer Guide
- Theming & Dark Mode
- ποΈ Recent β getting back to what you were doing
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
-
MobileβFriendly
- β³ π« Mobile: Tickets
- β³ π» Mobile: Assets
- β³ π Mobile: Calendar
- β³ π Mobile: Knowledge
- β³ π¦ Mobile: Service Status
- β³ πΌ Mobile: Watchtower
- β³ π§© Mobile: Problem Management
- β³ π Mobile: Change Management
- β³ πΏ Mobile: Software
- β³ β Mobile: Tasks
- β³ π Mobile: Forms
- β³ π Mobile: Contracts
- β³ π Mobile: Domains
- β³ π Mobile: People
- β³ π Mobile: Projects
- β³ ποΈ Mobile: Files
- β³ π Mobile: LMS
- β³ πΊοΈ Mobile: CMDB
- β³ πΊοΈ Mobile: Network Mapper
- β³ π§ Mobile: Process Mapper
- β³ βοΈ Mobile: Workflow
- β³ π₯οΈ Mobile: System
- β³ π Mobile: Reporting
- β³ π Mobile: System Wiki
- β³ π Mobile: Self-Service Portal
- β³ π§° Mobile: Techniques & Tricks
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- β³ π‘οΈ CSRF protection (S4) β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- π CardDAV contact sync
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π REST API: Domains
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ π·οΈ REST API: Cost centres
- β³ π§βπΌ REST API: Analysts
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ π Rota copy and paste β Developer Deep Dive
- β³ β Checklists & SOPs
- β³ βοΈ Mandatory fields
- β³ π·οΈ Ticket categories
- β³ π₯ Assigning tickets to a team, and escalation
- β³ π’ One board across every company
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ π Attachment size limits and large emails
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
-
β³
βοΈ Telegram channel - β³ β CSAT company scope and filters β Developer Guide
- β³ π₯ Microsoft Teams channel
- β³ π¨οΈ Mattermost channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ β Record previews
- β³ π Ticket notes: internal or shared
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ π¨ Telling the analyst a ticket is theirs
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π’ Ticket numbering
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ π Confidential tickets
- β³ π₯ Portal managers
- β³ π Who has seen a ticket
- β³ π Reading long tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ ποΈ The folder pane
- β³ π½ Just my tickets, or no closed ones
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- β³ π Scheduled work in your own calendar
- Problem Management
- Tasks
- π Projects
-
Assets
- β³ π’ Moving an asset between companies
- β³ π Shared asset locations
- β³ π§βπΌ Assigning assets to analysts
- β³ π Warranty and lease alerts
- β³ π Leases
- β³ π Saved table views
- β³ π¨οΈ Recording anything, and importing it
- β³ π·οΈ QR asset labels
- β³ π Who holds what, and handover documents
- β³ π₯οΈ The inventory agent (PowerShell)
- β³ ποΈ Proxmox VE servers
- β³ βοΈ VMware Cloud Director servers
- β³ π Linking equipment to tickets
- β³ βοΈ Follow-up tasks on a ticket
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
-
Forms
- β³ π¨ The form designer β Developer Guide
- β³ π Layout & the grid β Developer Guide
- β³ ποΈ Collections β grouping submissions
- β³ π Submissions as PDFs
- β³ β‘ What happens next β a form's own actions
- β³ π οΈ Sections & conditional logic β Developer Guide
- β³ π οΈ Lookup fields β Developer Guide
- β³ π‘οΈ Catalogue request approvals
- People
- Domains
- ποΈ Files
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ π’ One board across every company
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)
- What this is
-
π Bugs resolved
- β³ π #188 Transfer lock shown off when unknown
- β³ β #184 Subtask names and Overdue
- β³ π§© #186 Teams: the buttons did nothing
- β³ π Text in a change could run script
- β³ π AI settings changed without the AI permission
- β³ πͺ #178 Deleting an analyst failed
- β³ π« A team with no departments showed 0 tickets
- β³ π Projects: adding a phase did nothing on Save
- β³ π Leavers could still sign in
- β³ πΌοΈ Logo and courses broke on Apache with PHP-FPM
- β³ π’ Chat tickets ignored your ticket numbering
- β³ π Dates shown as a dash, or in server time
- β³ π Assets β Users showed people from other companies
- β³ π Restricted analysts could read other modules' data
- β³ πΌοΈ Replies with a picture in the thread failed to send
- β³ π Reply attachments never reached the customer
- β³ π οΈ Outbound email attachments β Developer Guide
- β³ π A global SSO provider was missing from the portal
- β³ π Behind a proxy, the SSO redirect said http
- β³ βοΈ The portal tagline moved when you saved it
- β³ π¨ The portal settings screen forgot what you saved
- β³ π‘οΈ The approvals inbox said "Error" and nothing else
- β³ π A table's answers were missing from the PDF
- β³ β A single-select column let you tick every option
- β³ π The portal ignored a form's field widths
- β³ π The tasks board stopped taking clicks
- β³ ποΈ #121 The index list is out of date after upgrading
- β³ π #133 The calendar subscription was empty
- β³ π #131 Tasks always reopened on the board
- β³ π₯ #129 Every page returned HTTP 500 after upgrading
- β³ π³ #127 A PHP warning above the System page
- β³ π #126 Notes stamped with the server's clock
- β³ π Storing every date in UTC
- β³ πͺ The portal was down for everyone signed in
- β³ βοΈ #120 Workflow notes could never be written
- β³ βοΈ #123 Three errors when running Database Verification
- β³ π #122 The description box was a stub in the corner
- β³ π£ Demo data deleted real accounts
- β³ π #117 Sign-in redirected to the wrong address
- β³ π¨ #108 The priority dot was invisible
- β³ β±οΈ #116 Time logged from the right-click menu
- β³ π #114 API keys refused by our own guard
- β³ ποΈ #110 Assigning a task told nobody
- β³ πͺ #107 Signed out while still working
- β³ π #103 "Share with Requester" reached nobody
- β³ π #102 Search found nothing for hyphens
- β³ πͺ #101 Source code editor opened behind
- β³ βοΈ #88 Subtasks could not be ticked off
- β³ π» #84 Asset deep link selected nothing
- β³ π« #79 A new ticket arrived with no status
- β³ π§ #79 A ticket from email did not say so
- β³ π #78 Bell opened to nothing
- β³ π¬ #77 Mail only collected from Inbox
- β³ π #74 The default password could not be changed
- β³ π¦ #70 Renaming an impact level
- β³ π€ #67 App-only mailboxes could not send
- β³ π #45 Verify only ever worked for Microsoft
- β³ π #45 IMAP reported as not authenticated
- β³ βοΈ An email template stopped escaping itself
- β³ π The portal dashboard showed the wrong time
- β³ π’ The folder said 99 and the list showed 96