Skip to content

Teams, roles and approvals, project fields, My Work (1.12.0 → 2.0.0) - #83

Merged
vincentmakes merged 184 commits into
mainfrom
feat/teams
Oct 26, 2026
Merged

vincentmakes merged 184 commits into
mainfrom
feat/teams

Conversation

@vincentmakes

@vincentmakes vincentmakes commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

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

  • A site often hosts several groups that plan independently; until now everyone saw everything at the site. Teams let people narrow the Gantt, Kanban, Staff and Equipment views to their group, remembered per site.
  • Tags, equipment and company events can be limited to teams, so a team-only closure no longer takes the whole site out of capacity.

1.13.0 — Roles, permissions and approvals

  • Three hard-coded roles could not express real organisations. Roles are now editable permission sets (59 keys), with project-manager rights on the projects a person manages.
  • Some teams want people to request their own bookings rather than make them: own bookings can need approval, decided by the PM and/or chosen roles, with a visible pending state that does not count in capacity.
  • Superusers and users now see only the sites they belong to, closing a long-standing data-visibility gap.

1.14.0 — Date picker

  • Typed dates and the native picker caused inverted ranges and missed holidays. One two-month calendar for every date shows weekends, bank holidays and closures, and an end can no longer precede the start.

1.15.0 — Features & defaults, change notifications

  • Admins needed instance-wide defaults (panel shown, Kanban on/off, Nager import, phase preselection) without code changes.
  • People were surprised by edits to their plan made by others; the booked person and the PM are now notified, coalesced so a drag is one line.

1.16.0 — Bookable as a resource

  • Whether someone appeared as staff depended on their role, so working admins could not be booked and non-working accounts cluttered the lists. It is now a per-user switch.

1.17.0 — Booking segments

  • A person often works a few days at the start and a few at the end of a phase. Bookings can be unlinked from the phase, carry their own dates, be booked several times without overlapping, and follow the phase when it moves.

2.0.0 — Project fields, My Work, searchable pickers

  • Tag groups were being stretched into structured project data. Admins now define real project fields (dropdown, multi-select, text, number, switch); the built-in fields can be renamed or hidden.
  • My Work replaces My Todos and focuses on what people act on: approvals to decide, their own assignments, their own requests. Bookings get a comment thread.
  • Every people picker gets a search box — with dozens of people per site, scrolling a list no longer scales.

Breaking changes (2.0.0)

  1. Tag groups became project fields. Existing groups are converted automatically on upgrade (single-select → dropdown, otherwise multi-select) with their items, colours, order, site/team limits and project selections; /api/tag-groups, tags.group_id and tag_groups are removed. Gantt filter/grouping/colour preferences saved before 2.0.0 reset once.
  2. Personal tasks removed before shipping. The todos feature of 1.13.0 (tasks, /api/todos, the todos.* 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 through run_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

  • Backend: 3682 passed; frontend: 3430 passed.
  • Suite floors raised to the new measured totals: fail_under = 91 in pyproject.toml, coverage.thresholds (lines 92.6, statements 91.5, functions 93.1, branches 83.3) in frontend/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 to contents: read. Two findings stay open by design for a decision: the one-time generated bootstrap admin password printed by init_db / master_db on a fresh install (documented, must be changed at first login).

vincentmakes and others added 30 commits October 21, 2026 11:34
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>
vincentmakes and others added 5 commits October 23, 2026 17:31
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>
vincentmakes and others added 5 commits October 25, 2026 16:13
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>
Comment thread app/routers/auth.py Fixed
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>
vincentmakes and others added 12 commits October 26, 2026 08:08
#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>
@vincentmakes
vincentmakes merged commit 5a1f1c0 into main Oct 26, 2026
25 checks passed
@vincentmakes
vincentmakes deleted the feat/teams branch October 26, 2026 12:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants