feat(time): the planned time entry is a satellite, not a third timeEntry - #575
Merged
Merged
Conversation
Three apps declared a `timeEntry`. A schema slug is global per organisation and `SchemaMapper::find()` matches `LOWER(slug)`, so whichever row was reached first answered for all three: humaniq's HR booking, pipelinq's billing record, and this app's project booking. humaniq is the agreed owner of the hours. This app's record is the PLANNING side of one — the task it was booked against, the contractor, the hourly rate — so it is named for what it is: `plannedTimeEntry`, carrying a `timeEntry` property naming the humaniq booking. That reference is a plain uuid and not a `$ref`. humaniq's register is a different register, and ADR-062 rule 7 gives a cross-register target no `$ref`. `RenameTimeEntrySchemaSlug` renames the row before `InitializeSettings`, modelled on pipelinq's step for the same collision (pipelinq#1805). Without it the descriptor change renames nothing: the import matches by (application, slug) and its not-found branch CREATES a second schema, orphaning every existing entry silently while the app reads an empty list. Registered in BOTH `<post-migration>` and `<install>` — the install block is the one that actually runs here, for the reason the file already documents about the `planix` -> `planninq` app-id change. Eighteen sites move together: the register's schema list, its key AND `slug`, six seedData `"schema"` references across the register and the mock, four manifest widget sources plus a `schemaSlug`, two stores and the portal contribution provider. Six existing tests asserted the old slug and now assert the new one — they are what caught the manifest and portal sites. WHAT THIS DOES NOT DO YET, and it is deliberate. `date`, `duration`, `user` and `description` stay here rather than moving onto the humaniq booking, because four dashboard widgets read `duration`, `user` and `date` straight off this schema, and moving them would make all four render nothing on an instance without humaniq — blank, with no explanation. The fleet already has the pattern: a `requiredApp` widget, which gate-55 supports and which HIDES rather than rendering empty when the backing app is absent — and pipelinq already reads THIS app's `project` that way. Applying it here is the next change, recorded in #572, and it is what makes the hours live once. The collision is cleared either way, which is what this change is for. 186 tests green (4 new, 6 updated). phpcs, phpmd, psalm and phpstan clean, and all 73 applicable hydra gates pass — after `composer update conduction/hydra-gates` (v1.14.0 -> v1.15.0); the stale vendored copy invented two manifest failures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
Quality Report — ConductionNL/planninq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ❌ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 536/536 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-05 13:00 UTC
Download the full PDF report from the workflow artifacts.
rubenvdlinde
added a commit
that referenced
this pull request
Sep 5, 2026
#575 renamed `timeEntry` to `plannedTimeEntry` — three apps declared a `timeEntry` and a schema slug is global per organisation. The seed's required-schema list still asked for the old name. That is the ninth hiding place a slug rename has, and the one that costs the most: the seed exits 1 before Playwright starts, so the whole suite is reported as NOT RUN rather than as failing. On development: ##[error]Planninq schemas missing after import: [timeEntry] ##[error]The e2e suite cannot seed a project, columns, tasks or labels ##[error]Seed command failed with exit 1 ...while the line above it listed `plannedTimeEntry` among the 28 schemas that had in fact imported. Verified against `lib/Settings/planninq_register.json` on development: every one of the six required slugs is declared. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
rubenvdlinde
added a commit
that referenced
this pull request
Sep 5, 2026
…580) `Portfolio renders capacity` clicked a nav entry titled "Portfolio". There is none: the report moved onto the Reports page as a card labelled "Capacity" — ADR-112, a report is a card OR an entry, never both — so the click waited out its 60s timeout on a locator that can never resolve, which reads as a broken page. This is the first failure this suite has ever reported. It never ran before: the seed asked for the `timeEntry` slug that #575 renamed, exited 1, and took Playwright with it. 1 failed, 34 passed is the first real tally. `openReportCard()` sits beside `navigateTo()` and addresses the card by its own testid and title span, NOT by the link's accessible name: CnReportsPage wraps title, description and category in one anchor, so an exact match on the label finds nothing. A liveness control goes first, so a card that does not match reads as a missing card rather than a page that never mounted. Verified: the "Capacity" card names page Portfolio, which routes to /portfolio — the URL the test still asserts. Co-authored-by: Conduction Release Bot <release-bot@conduction.nl> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Three apps declared a
timeEntry. A schema slug is global per organisation andSchemaMapper::find()matchesLOWER(slug), so whichever row was reached first answered for all three: humaniq's HR booking, pipelinq's billing record, and this app's project booking.humaniq is the agreed owner of the hours. This app's record is the planning side of one — the task it was booked against, the contractor, the hourly rate — so it is named for what it is:
plannedTimeEntry, carrying atimeEntryproperty naming the humaniq booking.That reference is a plain uuid, not a
$ref— humaniq's register is a different register, and ADR-062 rule 7 gives a cross-register target no$ref.The migration
RenameTimeEntrySchemaSlugrenames the row beforeInitializeSettings, modelled on pipelinq's step for the same collision (ConductionNL/pipelinq#1805). Without it the descriptor change renames nothing: the import matches by(application, slug)and its not-found branch creates a second schema, orphaning every existing entry silently while the app reads an empty list.Registered in both
<post-migration>and<install>— the install block is the one that actually runs here, for the reasoninfo.xmlalready documents about theplanix→planninqapp-id change.Eighteen sites move together
The register's schema list, its key and
slug, six seedData"schema"references across the register and the mock, four manifest widget sources plus aschemaSlug, two stores, and the portal contribution provider.Six existing tests asserted the old slug and now assert the new one — they are what caught the manifest and portal sites.
What this does not do yet, deliberately
date,duration,useranddescriptionstay here rather than moving onto the humaniq booking, because four dashboard widgets readduration,useranddatestraight off this schema. Moving them would make all four render nothing on an instance without humaniq — blank, with no explanation.The fleet already has the pattern: a
requiredAppwidget, which gate-55 supports and which hides rather than rendering empty when the backing app is absent — and pipelinq already reads this app'sprojectthat way. Applying it here is the next change, recorded in #572, and it is what makes the hours live once.The collision is cleared either way, which is what this change is for.
Testing
composer check:strict.composer update conduction/hydra-gates(v1.14.0 → v1.15.0). The stale vendored copy invented two manifest failures; worth updating before diagnosing one.🤖 Generated with Claude Code