Repository navigation
Teams, roles and approvals, project fields, My Work (1.12.0 → 2.0.0) - #83
Merged
Merged
Conversation
Teams belong to one site; users are members and projects, tags, company events and equipment can be linked to them. This lays the data layer for the feature: the six tables live once as tenant_provisioner constants that the fresh-install schema, the tenant auto-migration and add_teams.sql all carry, so existing tenants pick them up on their next pool creation. Every FK cascades, so deleting a site needs no extra cleanup. A parity test keeps the four DDL sources, the auto-migration and the ORM delete rules in lock-step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every router that links teams (projects, users, tags, equipment, events) needs the same lookups and site checks, so they live in one service. Tags restricted to teams at a site now only apply to projects that share one of those teams, and a count helper backs the 409 that will guard restricting a tag already in use outside its teams. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Teams need their own CRUD before projects, tags, equipment and events can link to them. Superusers manage teams only at their own sites, members must belong to the team's site, and team writes broadcast a "team" change so open clients refresh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Projects now carry team_ids on list and detail responses and accept them on create and update. Teams must sit at the project's site, and a team change re-validates the project's tags so a project never keeps a tag its new teams exclude. Unknown team ids are dropped because What-If replays may carry the id of a team deleted in the meantime. Also pins the teams router's members reload and synchronize_session=False in its tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
User responses now list the user's teams, and create/update accept team_ids so membership can be managed from the user side as well as the teams API. A user's teams must be at their sites, a superuser may only assign teams at sites they belong to, and removing a user from a site drops them from that site's teams so membership never outlives site access. Staff rows expose only the teams at the row's site, since a team belongs to one site. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tags, equipment and multi-site company events can now be limited to teams per site (no team at a site keeps the whole site). Teams must sit at one of the entity's sites (an all-sites tag may carry any), unknown ids are a 404, and narrowing a tag's teams under projects that would lose it is a 409 like the site rule. Narrowing only the sites drops the removed sites' teams without an extra query. COLOR_PATTERN moves to app/schemas/base.py so the tag schema can use TeamIds without an import cycle. The site-less project detail joins its sibling details in team_rules, and two combined-request tests pin the validation order on PUT /users and PUT /projects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The site export is the canonical snapshot of a site, so it now carries the site's teams (Teams, Team members) and the project links (Project teams), plus a Teams column on Equipment, Tags and Company events listing the entity's teams at this site (empty = the whole site). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The demo tenant now shows the Teams feature out of the box: two teams per site with members from that site, three projects linked to teams (one left without), the Frankfurt-only GMP tag limited to QA & Compliance, the shared Mass Spectrometer limited to the Analytical Lab at Winterthur only, the Plate Reader limited to QA & Compliance, and the Frankfurt town hall limited to the Formulation team. The loader inserts teams after users and every team link right after its parent's site links, all guarded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The backend now serves /api/teams and team_ids on projects, staff rows, equipment, tags, company events and users; the frontend needs the types and a client before the stores and views can use them. Team writes are configuration, so they bypass the What-If queue like tags: the project-only snapshot could not roll them back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The header team filter, tag scoping and closures all need the same rules for which teams count at a site (equipment, tag and event team ids are flat across sites). Keeping them as pure helpers gives every later view one tested source for those rules. getClosureOn's new parameters default to "no team data", which treats a team-restricted closure as a whole-site one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The header selector, views and settings panel all read teams from the app store and the per-site team filter from the view store, so both have to be loaded, persisted and kept live before any UI lands. The filter mirrors the tag filter (tenant + site key, sanitised on reload, kept across sign-outs). A team change reloads the slices whose team links a delete can cascade to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A site's teams narrow what people see, so the header gets a per-site team filter next to the site selector: Whole site, any of the site's active teams, or "(No team)". useTeamFilter is the one reading of the stored filter, pruned to the site's active teams, so the views and the dropdown agree; useTeamFilteredProjects hands back the same array while inactive to keep view memoisation stable. Also tightens two Task 10-12 leftovers: the optional-endpoints loader test now proves the teams catch landed, and teamFilterLabel drops an unreachable fallback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Teams need a home where admins and superusers create them, set their colour and active flag and pick their members. The panel lists one site's teams at a time from the sites the user may manage, so a superuser never sees another site's teams; delete confirms before the submit guard. The Users panel shows each person's teams read-only, since membership is edited in one place only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The header team filter only narrowed the selector itself. The Gantt, Kanban, Archived view, sidebar stats, resource panel and the Staff and Equipment overviews now show only what matches it, and the Cross-site view narrows the current site's lane only. The PM, assignment and equipment-block pickers follow it too but keep the currently selected item, so an edit never silently drops it. Also folds in the Teams panel follow-ups: a "no manageable site" empty state, one required-name message, a failed-create test, and the Users panel Teams cell pinned by column. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A closure restricted to teams must take only those teams' members off. Every getClosureOn caller (resource card, assignment bar markers, the staff panel's availability and the staff timeline rows) relied on the defaults, which fall back to the whole site and over-shade everyone else there; they now pass the person's teams and the team-to-site map. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Projects can now be given teams of their site in the project dialog, and
a project created while a team filter is active starts with that
filter's teams so it shows up in the current view. The tag picker
offers a team-restricted tag only to projects sharing one of its teams,
and drops it on save once that team is unticked.
TeamChecklist (shared with ProjectModal) and TeamScope ("Whole site" or
some of one site's teams) are the pickers the Settings panels reuse
next.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tags, equipment and company events could only be scoped to sites. Each form now offers, per site in scope, "Whole site" or some of that site's teams, so a tag, a piece of equipment or a closure can apply to a few teams only. Teams of sites that are no longer selected are dropped on save, and the tag list notes when a tag is limited to teams. The .ics import stays whole-site. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A closure limited to some teams shaded the whole-site header day as closed and its row tooltip claimed everyone on the site was out. The header now shades only whole-site closures and the tooltip names the teams concerned. Scoping follows the inactive-team ruling: equipment restricted to an inactive team stays scoped to it under a team filter, while the filter options remain active-only. The project dialog no longer posts a team deleted while it is open, stops writing a ref during render, and its Teams caption is no longer an orphan label. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Creating a multi-site event now rejects a bad date range before loading its teams, so an invalid request costs no team query. update_tag reuses the site ids it already computed, and the team-limited demo event gets a name that reads as a Formulation-only event. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Teams change what people see and what tags, equipment and closures apply to, so users, admins and developers need the rules written down: the per-site "no team = whole site" scoping, the inactive-team rule and the team-limited closure rule. One MINOR bump covers the whole feature branch. The two team screenshots are registered in the capture script but not yet captured, so the docs reference no image for them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The header team selector is hidden on a site without active teams, so a persisted selection (e.g. only "(No team)") would keep narrowing the views with no way to clear it. Treat any stored filter as Whole site there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deleting a team cascades its tag_teams rows. A tag linked to that team
and to other teams at the site then stays limited to the others, so a
project that reached the tag only through the deleted team would keep a
tag it no longer qualifies for. DELETE /teams/{id} now answers 409 with
the number of such projects, like the tag routes do when narrowing would
strand projects.
Also: team reads skip the members' own eager collections (the response
only needs member_ids), the demo comment on team-limited events no
longer claims non-members cannot see a plain event, and CLAUDE.md lists
the export's Tags sheet before Tag groups, as written.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Captured against the refreshed demo stack (Winterthur: Analytical Lab, Process Development) with scripts/screenshots/capture.py settings-teams team-filter; the release commit had left the two references out because the images did not exist yet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The header team filter only knew projects, staff rows and equipment, so team-limited company events and tags leaked through it. Events and tags follow the equipment rule (no team at this site = whole site), now one shared helper, and the hook exposes eventMatches / tagMatches plus useTeamFilteredCompanyEvents for the views to consume next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A company event limited to team B stayed on every timeline and in the Staff view's Company events row while the header filter showed team A: the views built their cells from the unfiltered store. They now read useTeamFilteredCompanyEvents(). The tag filter dropdown no longer offers tags restricted to filtered-out teams, and the Kanban card dialog's "Add someone" list follows the filter on cards of the current site. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Narrowing the tag options by the header team filter also narrowed the list projects were evaluated against, so a visible project carrying a tag of a filtered-out team counted as having no tag of that group and matched "(No tag)". The options stay narrowed; resolution uses every tag of the site. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After limiting a company event to a team nothing showed which team: the Settings events table had no Teams column, and the Staff view's Company events row named teams only in a closure badge tooltip. The table now lists the teams (or "Whole site") and the row names them after the event. The changelog, user guide, CLAUDE.md and a trail learning record that the header team filter now also covers team-limited events and the tag filter options. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
team_ids is a flat cross-site list, so at site A an event shared with site B and limited only to a B team showed "· <B team>" although it is whole-site at A, as the team filter treats it. The row label and the Closure tooltip now resolve against the current site's teams. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The filter now hides team-limited company events and narrows the tag filter's options. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Editable roles, approvals and My Todos need one source of truth for permission keys and a schema every later task can build on. The catalogue (app/services/permissions.py) lists every key with its group, label and shipped defaults, which reproduce today's superuser and user behaviour so an upgrade changes nothing; shared/permission-keys.json mirrors the spellings for the frontend. The roles, role_permissions, approval policy, approval request and todo tables, the assignment approval columns, the widened notification types and the users.role CHECK -> FK swap land in every DDL source with add_rbac.sql, the tenant auto-migration and a data-migration seed step. System roles get their default permissions only when the role row is created by the same statement, so an admin's edits are never re-seeded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first pass fixed the behaviour, but CodeQL still flagged both rules on the PR: it reads any value held in a variable named like "secret" as sensitive (the set/missing flag was one), and it only accepts a redirect target as sanitised after backslashes are stripped and urlparse() finds no host. Rename the flag, and route the tenant home path through _local_redirect_path(), which now does exactly that check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The descriptive half of CLAUDE.md (project tree, schema, routes, auth and tenancy, real-time, frontend architecture, the long convention bullets) is copied verbatim into docs/developer-guide/ under a new "Codebase Reference" nav group, with prose cross-references turned into links. Anchor validation is on, so a broken link fails the strict build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md drops from 21 256 to about 5 900 words: it keeps every rule, the checklists, Quick Start, Common Commands and the Gotchas, adds a "Start here" map to the Codebase Reference pages, and carries in as one-liners the rules that lived in the moved sections (site export, no FK on polymorphic rows, no fallthrough to DB_*, session revocation, unauthenticated settings, workflow checks, What-If reloads). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/developer-guide/ai-workflow.md describes how feature rounds run with agents (round shape, model routing, verification policy, sandbox notes) and joins the Codebase Reference nav; CLAUDE.md, CONTRIBUTING.md and the contributing page link it. frontend/README.md, README.md and docs/index.md now point at the new reference pages, and a Trail learning records the section-to-page map and how to resolve the CLAUDE.md conflict when the feature branches land. Docs-only: no version bump. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tenant admins can overwrite a working Microsoft Entra configuration with nothing checking it against Microsoft. The organization test in the admin portal already does that check, inline in its route and for saved values only. Move it to app/services/sso_test.py, parameterised on plain values, and expose it to tenants as POST /api/auth/sso/test (settings.sso; 409 while organization SSO is active) for the values entered in the form. A blank or masked secret tests the stored one, the rule the save routes apply, and SSOConfigUpdate now treats a whitespace-only value as "keep" so a green test of a blank secret cannot be followed by a save that clears it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Some confirmations guard changes that are easy to click through and hard to undo. confirmAction() gains a confirmWord option: the dialog shows a field, keeps the confirm button disabled until the word is typed, and confirms on Enter once it matches. The field lives in a child keyed by the request id, so the next queued request never opens pre-filled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An admin could overwrite a working Microsoft Entra configuration with one click and nothing checked the values. Settings → SSO now has a Test configuration button that sends the entered values to POST /api/auth/sso/test (a blank secret tests the stored one) and shows the checks in a result component shared with the admin portal's organization test. Over a live configuration, saving a change that can break Microsoft sign-in (the ids, the secret, the redirect URI, switching SSO off) first asks for the typed word CHANGE and says who is affected; role and auto-create changes save as before. The panel reports unsaved input to the Settings hub. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The admin and user guides explain Test configuration at both levels and the CHANGE confirmation over a live tenant configuration; CHANGELOG 2.0.0 gains the Added and Changed lines. CLAUDE.md lists the new route, the shared diagnostic and confirmWord, and corrects the organization test's response key (credentialsOk, not credentials_ok). The learning records the Test/Save secret rule and why the typed field is keyed. trail check flags CLAUDE.md:74 against the Oct 17 learning that ruled out one shared Gantt scroller panel: a keyword match only — the line is the confirmAction() convention, which gains confirmWord and no layout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nization SSO Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CodeQL flagged the tenant SSO test's log line as clear-text logging of a secret (py/clear-text-logging-sensitive-data, alert #26 on PR #83). The logged value only says where the tested secret came from ("stored" or "entered"), but the variable was named secret_source and the rule treats any value held in a variable named like "secret" as sensitive, the same heuristic 7af920f worked around. Rename it to value_origin and give it its own assignment, apart from the secret's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vincentmakes
added a commit
that referenced
this pull request
Oct 26, 2026
Touching frontend/README.md was the only reason the frontend workflow ran on this docs-only change, and its Audit job then failed on npm advisories that sit on main's dependencies, not on anything here (PR #83 brings the fixed vite and vitest). The README's pointer to the new Frontend architecture page moves into #83, which merges this split next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#85 lands on main first, so this branch takes the short CLAUDE.md core and the Codebase Reference pages now. The only conflict was CLAUDE.md: the pages are #85's pages regenerated from this branch's CLAUDE.md with the same slicing (so every teams-era sentence moved verbatim), and the core is #85's core plus the teams rules (permission keys, visible sites, approvals, change notifications, booking segments, icons, form fields, the SSO test, confirmWord), condensed with links into conventions-in-depth.md. The two schema-source tests now read the migration history from docs/developer-guide/database-schema.md. trail check flags four CLAUDE.md lines (137, 152, 242) by keyword overlap only (tenant row, one shared panel, demo/1440); none restores a ruled-out approach. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vincentmakes
added a commit
that referenced
this pull request
Oct 26, 2026
* Move the CLAUDE.md reference into seven Codebase Reference pages The descriptive half of CLAUDE.md (project tree, schema, routes, auth and tenancy, real-time, frontend architecture, the long convention bullets) is copied verbatim into docs/developer-guide/ under a new "Codebase Reference" nav group, with prose cross-references turned into links. Anchor validation is on, so a broken link fails the strict build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cut CLAUDE.md to a core of rules and pointers CLAUDE.md drops from 21 256 to about 5 900 words: it keeps every rule, the checklists, Quick Start, Common Commands and the Gotchas, adds a "Start here" map to the Codebase Reference pages, and carries in as one-liners the rules that lived in the moved sections (site export, no FK on polymorphic rows, no fallthrough to DB_*, session revocation, unauthenticated settings, workflow checks, What-If reloads). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add the AI workflow page and point the READMEs at the reference pages docs/developer-guide/ai-workflow.md describes how feature rounds run with agents (round shape, model routing, verification policy, sandbox notes) and joins the Codebase Reference nav; CLAUDE.md, CONTRIBUTING.md and the contributing page link it. frontend/README.md, README.md and docs/index.md now point at the new reference pages, and a Trail learning records the section-to-page map and how to resolve the CLAUDE.md conflict when the feature branches land. Docs-only: no version bump. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the last agent rules and the screenshot rules in the CLAUDE.md core Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Leave frontend/README.md to PR #83 Touching frontend/README.md was the only reason the frontend workflow ran on this docs-only change, and its Audit job then failed on npm advisories that sit on main's dependencies, not on anything here (PR #83 brings the fixed vite and vitest). The README's pointer to the new Frontend architecture page moves into #83, which merges this split next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Vincent Verdet <vverdet@MacBookPro.localdomain> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#85 landed on main as one squash commit, so git sees its CLAUDE.md split and the new pages as unrelated to the copy this branch already merged (7220e01, from #85's branch), and every one of those files conflicts. This branch already holds all of #85's content plus the teams-era additions placed into the split, and main holds nothing else: 09b66dc equals #85's final head, which differs from the head merged in 7220e01 only by dropping the frontend/README.md pointer that this branch deliberately keeps. So the merge keeps this branch's tree as it is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In the project dialog the Teams checklist sat between the built-in fields and the workspace's own project fields. It now follows every project field and comes right before Tags, which it scopes. A dropdown item limited to a team is still offered only once that team is ticked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Confirmed sat at the bottom of the dialog next to Archived. It now follows the project fields and comes right before Teams and Tags; Archived, shown only when editing, stays at the bottom on its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The comment route now builds its row and notifications through pure helpers (new_comment, build_comment_notifications, comment_preview) that the booking paths will reuse; behaviour and the tests' patch targets are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A direct create posts the comment to the booking's thread as the actor's comment (kanban.comment, 403 before any write), notifying mentions and the thread's assignees but leaving the booked person and the PM to their change notification, which now carries the preview. In approval mode the comment is the request's note: the approvers see its preview, and approving a create posts it as the requester's comment when they may comment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
equipment_assignments.note (TEXT) in every schema source, a verbatim migration and its own tenant auto-migration step. Create and PUT accept note (PUT: left out keeps it, null or blank clears it; a note-only edit notifies nobody); responses, the booking list, the project tree and the site export carry it, and the PM's notification of a new booking shows it. Route, API, schema and approval docs describe the staff comment and the equipment note. trail check flags three false positives: assignments.py:73 is docstring text (nothing is split at submit), approvals.py reads the requester's role only to load their permission set (no role-name map), and the new tenant_manager step is its own self-gated step, outside the RBAC block. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new staff booking can carry a comment (mentions picked from the people on the card, then the site, through a useMentionCandidates hook now shared with CardComments): a direct booking's comment shows only with kanban.comment, a request's message for the approver always. Equipment bookings get a note on create and edit. Equipment is now always booked on the project: phase and subphase rows used to post to routes that do not exist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
My Approvals renders the requester's note through MentionText, and My Requests shows the requester's own message as "Your message", apart from the decision note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
User guides for the dialog's comment, the request message and the equipment note, the 2.0.0 changelog lines (including the equipment booking fix), the approval rule's request-note sentence in CLAUDE.md, the hook in the frontend architecture page, and the learning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A request's @-mentions notify nobody until it is approved, and only if the requester may comment, so the request placeholder no longer says "Type @ to notify someone": a commenter reads that mentions are notified once the request is approved, anyone else is asked why they need the booking. The field is named by a <label htmlFor> (MentionTextarea takes an id) instead of an aria-hidden span, comment_preview is typed for the None it receives, and the CHANGELOG line states the comment and approval conditions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Gantt right-click items booked in one click, so people who cannot manage the project never reached the comment or the message for the approver added to the assignment dialog. The four items (and their "again…" forms) now open that dialog with the person and their capacity preset; left untouched it sends the same booking as before, a second booking on a phase still without dates for the server to place, and the dialog says so. Placement refusals now show in the dialog, which stays open, instead of a flash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An editor opened for someone ("Assign me…" for a role that manages
others' bookings without editing the project) had that person preset but
missing from the picker when they work at another site or fall outside
the team filter: the field read "Select staff member" while submit still
booked them. The person the dialog was opened for now stays listed, like
an edited booking's. Also names the menu entry "Assign me…" in Settings
and lifts the second-booking notice into a helper.
Co-Authored-By: Claude Opus 5.5 <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.
This branch carries every release from 1.12.0 to 2.0.0 (157 commits). Each one answered a concrete gap that labs running several teams on one site kept hitting; the per-release detail is in
CHANGELOG.md.Why, release by release
1.12.0 — Teams
1.13.0 — Roles, permissions and approvals
1.14.0 — Date picker
1.15.0 — Features & defaults, change notifications
1.16.0 — Bookable as a resource
1.17.0 — Booking segments
2.0.0 — Project fields, My Work, searchable pickers
Breaking changes (2.0.0)
/api/tag-groups,tags.group_idandtag_groupsare removed. Gantt filter/grouping/colour preferences saved before 2.0.0 reset once.todosfeature of 1.13.0 (tasks,/api/todos, thetodos.*permissions and task notifications) is gone; My Work keeps only approvals, assignments and requests.Upgrade
Automatic: the tenant auto-migrations create the team, RBAC, booking-segment and project-field schema and convert tag groups on first pool creation (single-tenant: at startup). The SQL files (
add_teams,add_rbac,add_change_notifications,add_user_is_resource,add_booking_segments,add_project_fields) remain available throughrun_migration.py. The demo seed (app/scripts/demo_data.py) covers the new data — teams, roles and a pending approval, unlinked bookings, project fields — and the docs screenshots were refreshed from it.Tests and coverage
fail_under = 91inpyproject.toml,coverage.thresholds(lines 92.6, statements 91.5, functions 93.1, branches 83.3) infrontend/vitest.config.ts.Code scanning
This PR also closes the open CodeQL findings that were real: the SSO log lines carried a truncated digest of the client secret (now only "set/missing"), SSO redirects now accept only tenant slugs and same-origin paths (a configured redirect URI whose path began with
//could become a scheme-relative redirect), and the workflows default tocontents: read. Two findings stay open by design for a decision: the one-time generated bootstrap admin password printed byinit_db/master_dbon a fresh install (documented, must be changed at first login).