Licensed features can now be granted or revoked per user instead of
applying instance-wide. Resolution is layered: the instance license must
include the feature, then a per-user override, an admin-set instance
default and the built-in code default are consulted in that order.
Time tracking is the first per-user toggleable feature; admin_panel and
audit_logs stay instance-wide. New features opt in via the
perUserToggleable map in pkg/license.
- store per-user overrides in a json column on users, instance defaults
in the new pro_feature_instance_defaults table
- enforce the toggle in the v2 time-entries route gate and in the
TimeEntry permission chokepoint for non-route callers
- new admin v2 endpoints to manage instance defaults and per-user
overrides
- expose effective_pro_features on /api/v1/user; the frontend prefers it
over /info's instance-wide list once the user is loaded
- admin UI: per-user toggles on the user detail modal, instance defaults
on the admin overview
https://claude.ai/code/session_01AVt4FHWrUUhv5p6yn99pdp
Bot owners inherit read/update/delete permission on labels created by
bots they own, mirroring the bot-owner branch already used by API tokens
(see api_tokens_permissions.go). Without this, a label a bot creates is
permanently locked to that bot and the human owner cannot maintain it.
https://claude.ai/code/session_016x6mUPJuuQEeXpHY814iLh
The OpenID callback view used a localStorage "authenticating" flag to avoid submitting the same authorization code twice when the route was remounted during an auth layout swap.
That layout swap is now guarded by AUTH_ROUTE_NAMES, so openid.auth stays in the unauthenticated shell until redirectIfSaved() navigates away. The persistent flag can instead get stranded when the page is refreshed, closed, or interrupted during the callback, making future OIDC callbacks silently return before exchanging the code.
Remove the flag so each valid callback URL is processed normally while keeping the existing state validation and TOTP retry handling.
The admin-toggle handler delegates to handler.DoUpdate — the same pipeline
v1's UpdateWeb wraps — instead of re-implementing the session/permission/commit
orchestration. TeamMember.Update now carries the persisted row back onto the
receiver so both v1 and v2 responses include id/created.