The rebrand had reached the product's own words and stopped at its links, so
every screen that offers help still handed the reader to the other vendor:
the training menu opened their academy, Report a Problem composed mail to
their address and cited their Zendesk articles, Contact Support opened their
ticket form, and the OpenID, SAML, LDAP, theme and API-token pages linked
their documentation. Three emails we send asked people to write to their
support and feedback addresses, and the invitation footer — the most-read mail
in the product — linked their homepage.
Where the docs host was already ours the slug still was not, so a reader saw
docs.hanzo.team/upgrade-mattermost in the status bar. Those keep the host and
lose the name.
The admin console examples too: an S3 bucket called mattermost-media, an
allowed-domain list of corp.mattermost.com, a subpath of /company/mattermost.
Two constants named the other product and pointed at ours, which is the same
contradiction one level down; one had no readers at all and is gone.
WHAT STAYS, and why each is not branding:
*.push.mattermost.com the HPNS endpoints, a service that is theirs
translate.mattermost.com the Weblate these translations come from
mattermost ldap idmigrate, ./bin/mattermost, mattermost.log
the command, the binary and the file, by their names
MattermostAppID, {{.MattermostUsername}}
a JSON field on the API and a template variable
the copyright headers a licence notice
en.json is GENERATED, and the committed one had drifted from the sources in
three places: two keys edited there and not in the source — the app showed the
new string and the extractor put the old one back — and one pair the sort
comparator orders the other way. Regenerated, so the two agree again.
Co-authored-by: Hanzo Dev <[email protected]>
Renaming the placeholder in the claim set left the two assertions below it
still reading Zach Kelling, so the one gate this repository declares has been
red since 2026-09-01 and says so the moment anything runs it.
Co-authored-by: Hanzo Dev <[email protected]>
server/go.mod reached github.com/hanzoteam/agents/v2 through a replace naming
an absolute path under one machine's agent scratchpad. That directory exists on
no other machine and on no runner, so every build off it opens go.mod and stops:
reading .../scratchpad/agents/go.mod: no such file or directory
The fork itself is pushed — hanzoteam/agents master declares the rebranded
module path — so a pseudo-version pins the same tree, verified against the
checksum database instead of a filesystem. Its newest TAG cannot be used: v2.5.1
predates the rename and still declares mattermost-plugin-agents, which is both a
path mismatch and the upstream public module coming back into the graph.
Measured after: the binary builds, `mattermost version` answers, and
`go list -deps ./cmd/mattermost` names one public/plugin, ours, with no package
from mattermost/mattermost in it at all.
Co-authored-by: Hanzo Dev <[email protected]>
The two branches each held work the other lacked: main carries the fork's
rename and its published package scope, master a two-line fixture change
under the same test. Building from either stranded the other.
Co-authored-by: Hanzo Dev <[email protected]>
Bylines, paper authorship, correspondence addresses and the founder blurb under
each post named an individual. They name the organisation that published the
work instead: the brand on posts and pages, the research byline on papers, and
research@ in place of a personal address. Both are what these repositories
already used elsewhere — nothing new was coined.
Co-authors are untouched. Where a paper named someone else alongside, they stay
named; only the one byline changed. Bibliography entries, running heads,
PDF metadata and affiliations move with it, so a citation still resolves to
whoever the cited paper names.
Sample and fixture data uses the placeholder name these tests already use
elsewhere, so no assertion changed meaning.
Co-authored-by: Hanzo Dev <[email protected]>
The server module named github.com/hanzoai/team/server/v8 — a path that
resolves to the retired workspace's repository, not this one — while
server/public still published github.com/mattermost/mattermost/server/public.
Both are github.com/hanzoteam/server/... now, which is where this code
actually lives.
That second half is what made the binary unbootable. Upstream's public path
stayed claimed by upstream, mattermost-plugin-ai imported it, and the build
linked public/model twice; public/plugin's gob.Register then ran twice under
one name and panicked at init, before main. Reverting the rename traded the
brand for a working binary. Rebranding the plugin alongside it needs no such
trade: the agents fork is github.com/hanzoteam/agents/v2 against our public
module, so no module named mattermost/mattermost is in the graph at all.
Measured: `go list -deps ./cmd/mattermost` names exactly one public/model, and
it is ours.
Renaming server/public to github.com/hanzoai/team/server/public left the old
path unclaimed, and mattermost-plugin-ai still imports it — so the binary
linked public/model TWICE, ours and upstream's, and public/plugin's
gob.Register calls ran twice under one name:
panic: gob: registering duplicate types for "[]*model.SlackAttachment":
[]*model.MessageAttachment != []*model.SlackAttachment
at init, before main. The server has not been able to start since that rename
landed, which is why the deployment is still answering from an older build.
server/v8 keeps its new name; nothing outside this repo imports it. public
does not, because its path is not a brand — it is the plugin API, and every
plugin written against Mattermost imports it by that name. Our own agents
fork is proof: it carries a replace pointing that exact path back here.
Reverting the public half is what makes the two copies one again. Measured
after: `go list -deps ./cmd/mattermost` names exactly one public/model, the
binary starts, and it serves.
The msgpack decoder's allocation limit is dead code upstream. decode_slice.go:104
reads `d.flags&disableAllocLimitFlag != 1`, and that flag is the fourth bit of a
`1 << iota` block — value 8 — so the test can never be true and the 1e6-element
clamp under it never fires. Line 52 of the same file gets it right with `!= 0`,
which is how you can tell it is a typo and not a decision.
The server module already carried the corrected fork. This module did not, and a
replace governs only the module that declares it — so server/public, which other
code imports directly and by version, resolved the decoder whose ceiling does not
work. It is the module holding WebSocketRequest, whose Clone() msgpack round-trip
runs on every inbound frame, and whose Data field is the map[string]any a caller
supplies.
Both modules build; server/public now resolves to the patched decoder.
Recorded while establishing that msgpack cannot be replaced by ZAP here: the only
reflection codec in zap-proto/zip lives under internal/ and cannot be imported,
and it refuses reflect.Map and reflect.Interface outright — which is precisely
what Data is. The pin is load-bearing, so it is worth having it correct.
Co-authored-by: Hanzo Dev <[email protected]>
Every package in this fork announced itself as github.com/mattermost/mattermost —
in 4,553 import lines, in both go.mod module declarations, and in the workspace.
A fork whose own import path names another company is one nobody can read as
theirs, and it is the first thing anyone sees at the top of every file.
server/v8 and server/public are now github.com/hanzoai/team/server/…, rewritten
mechanically and verified: `go build ./...` is clean for both modules and `go vet`
passes.
Three things are deliberately UNTOUCHED, because they are other people's names
rather than our branding: mattermost-plugin-ai and mattermost-plugin-mscalendar
are real dependencies we consume, github.com/mattermost/msgpack is a real
dependency, and the github.com/mattermost/mattermost/pull/… links in comments
point at real upstream discussions that renaming would turn into dead links.
server/public needs a `replace` to resolve locally now: the require still names a
version, and no proxy serves our path. The workspace alone does not satisfy a
require, which is exactly how this failed the first time.
Co-authored-by: Hanzo Dev <[email protected]>
Roles were a local fact. The Hanzo provider mapped a person's claims onto a user
and never touched Roles, CreateUser then wrote system_user unconditionally, and
the store reverts Roles on any untrusted update — so authority was set once, at
account creation, and nothing ever revisited it. An operator granted the reserved
org in IAM stayed an ordinary user here, and, worse in the other direction, one
demoted in IAM kept administering this server forever. A grant that cannot be
revoked at the issuer is a second authority model, which is the thing there is
supposed to be exactly one of.
The predicate is authz.Claims.PlatformSudo rather than a comparison written here.
Platform authority is MEMBERSHIP of the reserved org, held at any position in the
set, which is not the same question as which org someone is anchored in: an
operator is anchored where they do ordinary work and holds the reserved org
alongside it, so reading `owner` grants nothing to every real operator. That is a
mistake this fabric has already made once; importing the issuer's own predicate is
how it is not made a second time, and it also keeps the verbatim org comparison,
without which any org a customer can name themselves ("Admin") is the reserved one.
Applied beside the org claim in LoginByOAuth, which already re-reads that claim on
every sign-in and covers both a new account and a returning one. A provider that
states no roles leaves the stored value alone, so this is inert for every provider
but ours, and an identity carrying no membership set is not an operator — so it
fails closed and can ship before or after IAM starts sending the claim.
Co-authored-by: Hanzo Dev <[email protected]>
Sign-out forgot the session here and stopped: it revoked the server session,
cleared the cookies, and pushed a route. The issuer's session was never told,
and while it stands the next visit is admitted with no prompt — so the person
appeared never to have left, which is what "logout is broken" means.
Ending it has to be a navigation. The issuer keeps its session in a SameSite=Lax
cookie, which a browser presents on a top-level navigation and withholds from
anything a script sends, so a request would arrive with no session attached,
find nothing to end, and answer 200 regardless. A route push never leaves the
document at all, so it could not work by construction.
The address is derived from the authorize endpoint rather than configured beside
it, the same way AcceptsPassword derives from one switch: authorize and logout
are siblings at the issuer, so a brand pointing this server at its own hanzo.id
carries the matching logout with it and the two cannot be set to disagree. When
this server owns the session the address is empty and the push is the whole
story.
The login page offered a password form beside the hanzo.id button, and the
login endpoint accepted the password behind it. Running hanzo.id now retires
the password: no login id resolves to one and the client is not offered the
form, from a single reading of the config.
The button says what it does, and the card stops saying "one of the
following" when it lists one thing.
An account created with a password was refused at the door: the OAuth path
returned already_attached before it ever asked the provider whether the
account was this person. hanzo.id issues every identity here and vouches
for the address, so the account holding it takes on the identity and keeps
its history. Writing the auth data drops the password with it.
An account already carrying a different subject stays with it, so an
identity is never moved from one subject to another.
Plugins are separate processes. They read "Mattermost-User-Id" off the
request as a literal string, compiled against the published server/public
module -- not against this tree. Renaming it here did not rename it there.
It also produced a header name with a space in it, which is not a valid
header name, so what shipped was a request the plugin could not read and
would not have been able to read under any spelling. Every plugin HTTP
route answered 401: the Agents API, its admin config, the webapp's whole
agents surface.
The same rename hit "Mattermost-Plugin-ID", the push proxy's
"X-Mattermost-Auth" and "X-Mattermost-ServerID", and the session
attribute device header. These are wire protocol, and the wire is shared
with software that is not ours to rename.
Brand strings the product actually shows -- the site name, the MFA
issuer, email product names -- are untouched. The outbound user agent
stays ours but loses its space: HanzoTeam-Bot/1.1 is a product token,
"Hanzo Team-Bot/1.1" is two.
Co-authored-by: Hanzo Dev <[email protected]>
The Dockerfile ADDs one plugin bundle, agents. Calls and playbooks were still
defaulted on from upstream, so the server enabled two plugins it does not have:
the System Console listed them as enabled, and neither could ever start.
The NPS default beside them was already examined and turned off. These two were
carried forward unread.
Turning them off does not remove a feature. There was no feature -- there was a
setting claiming one.
Slack's names, which is what people expect to find when they arrive. The
channel names and their display names move together: town-square -> general,
off-topic -> random.
The name list existed twice, verbatim, in App.DefaultChannelNames and
TeamService.DefaultChannelNames, with a third copy of the literals in the web
app's constants and a fourth in the display-name map. All four now read
model.DefaultChannelName / model.RandomChannelName, so the server and the
client cannot drift apart about which channel a member may not leave.
Existing teams keep the channels they already have -- these constants are read
when a team is created, not on boot, so nothing is renamed underneath anyone.
hanzo.team promises sixteen AI coworkers with jobs -- Vi leads, Dev
writes code, Sec looks for the way in, Data says what a dataset will not
support. A new workspace started with one bot named after the vendor.
The roster now lives in the server, so a workspace can create those
accounts without asking a website what they are called, and the product
a reader is promised is the product they get.
They are definitions, not processes. Sixteen idle coworkers is sixteen
rows: the plugin spends tokens when one is addressed and not before, so
having a full team costs the same as having none until you talk to them.
One service backs all sixteen, so the model and the key are changed in
one place. Each brief names its coworker -- a brief that does not is
sixteen identical assistants wearing different labels, which the tests
check for along with the roster matching the page.
Co-authored-by: Hanzo Dev <[email protected]>
The default named https://push.hanzo.team, which does not resolve and
which nothing in the fleet serves. A default pointing nowhere is worse
than an empty one: the server retries a dead host on a schedule and the
operator reads the failures as a push bug rather than as push being
unconfigured.
Empty says the true thing. Set it when a relay exists.
Co-authored-by: Hanzo Dev <[email protected]>
The base moves off /v1/team for two reasons that point the same way.
It collided: /v1/team on hanzo.team is a carve-out routed to the older
cloud service, so the moment this image shipped its own API would have
been answered by something else -- a 404 on our own endpoints, on the
host we serve from.
And workspace is the better noun. It is what the product calls itself
in its own words, and it does not read as though the API belongs to one
team among several.
No /api/ anywhere in a path we own.
Co-authored-by: Hanzo Dev <[email protected]>
The /api/v3/oauth/{service}/complete route existed to keep SSO working for
installs that predate the move to /login/ and /signup/ completion URLs. No
such install exists here, and this fork does not carry back-compat routes,
so the route is gone rather than renamed. The two live completion URLs are
the ones an operator registers with the identity provider, and the comment
now says that instead of calling them legacy.
Push notifications go to push.hanzo.team by default, which makes that
service ours and its versioning ours to pick: /v1, like everything else we
serve. The push handler in the tests now derives its routes from the
constant so the two cannot drift apart again.
The WebSocket client's doc comments described a /api/v3 base that has not
been real for some time. They describe the base the client actually builds.
Co-authored-by: Hanzo Dev <[email protected]>
Both were mine, left behind by earlier changes rather than pre-existing.
NPS reports survey results to Mattermost, so this server does not enable
it. The test still asserted upstream's behaviour -- on by default, and
coupled to the diagnostics setting. It now pins the decision, including
that diagnostics no longer turns it on: the two were coupled, which made
the survey read as a consequence of telemetry rather than a choice of
its own.
The image-proxy round-trip stopped round-tripping. The rebrand rewrote
the plain image URL to hanzo.ai but not the copy encoded inside the
proxied URL beside it, so the test asserted that unproxying one host
yields a different one. They name the same host again, and the example
site URL stops being upstream's.
Co-authored-by: Hanzo Dev <[email protected]>
The product namespace is team, and /api/ is not used as a path prefix
here — API hosts carry the api.* name and paths carry the version. This
moves the one constant every route mounts on, so the public router, the
local router and the websocket endpoint all follow it.
The two 404 catch-alls had the old prefix written out by hand, which
meant the constant was not actually the single source of truth; they now
derive from it, as do the emoji, image-proxy, remote-cluster and
self-ping URLs that were each carrying their own copy of the literal.
The remote-cluster path constants gain a leading slash from the shared
constant, so the two call sites that joined them with an explicit
separator would have produced a double slash and are adjusted.
APIURLSuffixV4 was a bare alias of the value and APIURLSuffixV5 mounted
a subrouter that no route ever registered on, so both are gone rather
than carried forward under names that no longer describe anything.
APIURLSuffixV1 is not ours at all — it addresses the external push
notification service — so it keeps its value and is renamed
PushURLSuffix to stop it reading as a version of this API.
Co-authored-by: Hanzo Dev <[email protected]>
The gate failed on `[build failed]` for symbols that are in this tree. Upstream
publishes server/public and pins v0.4.0 of it; we changed server/public, so
anything that reads the pin compiles against a release with no HanzoSettings, no
ServiceHanzo, no UserAuthServiceHanzo. The workspace is what makes ./public mean
this tree, and it was ignored — so `go build`, `go test`, the gate and the image
each had to arrange it privately, and the one that did not simply failed.
Tracking it states the fact once. The image build no longer arranges anything.
Also:
-tags production. Without it server/public/model selects the DEV service
environment through a build constraint, and a shipped binary points at dev
telemetry and licensing while looking entirely normal.
The runtime tree — data, logs, config, plugins, client/plugins — is made in a
stage that has a shell, because distroless does not and the server has nowhere
to write without them.
The document-extraction binaries are dropped rather than carried. pdftotext
wants GLIBC_2.38 and libstdc++ on a 2.36 base that has no libstdc++ at all;
wvText is a shell script and there is no shell. The library COPYs beside them
read /usr/lib while Ubuntu keeps those under /usr/lib/x86_64-linux-gnu, and a
--from glob matching nothing is a silent no-op — so upstream's image ships five
binaries with no libraries and reports success. Restoring extraction needs a
base that can run it; that is its own decision.
.dockerignore, because .git alone is 1.2 GiB of context and node_modules is a
working tree's results standing in for the build's.
Co-authored-by: Hanzo Dev <[email protected]>
Renames the product across user-facing text -- 1475 lines, i18n and the
defaultMessage strings that must match it -- while leaving the names that
are code rather than prose: upstream copyright, Go import paths, the
@mattermost npm scope, i18n keys, Go template fields, and legacy theme
identifiers like AuthorType.Mattermost and the 'Mattermost' key that maps
an old theme to a new one. The rule is a word boundary on identifier
characters; MattermostEntry is a symbol, "Mattermost" in a sentence is
the product.
The theme goes monochrome. Hanzo and Hanzo Dark are the two built-ins and
Hanzo is the default, with the CSS custom properties that carry brand
colour outside the theme object -- sidebar, buttons, links, titles --
moved to black. Status colours stay: online, away, do-not-disturb and
error encode state, not brand, and flattening them to grey would cost
meaning to buy consistency.
The sign-in header drops the TEAM EDITION badge. It advertised which
licence tier you were on, and there are no tiers here. Booting without a
licence is now logged at debug rather than warned about, because it is
this server's normal condition rather than a fault.
Co-authored-by: Hanzo Dev <[email protected]>
Agents arrives configured against api.hanzo.ai with the model "best" --
the gateway's own routing alias, so the default does not go stale as
models come and go. The API key is deliberately absent; it is delivered
from KMS at deploy.
NPS sends satisfaction surveys to Mattermost. It was enabled whenever
diagnostics were, and diagnostics now default off, but the coupling read
as an accident waiting to be re-enabled. It is off on its own terms.
Co-authored-by: Hanzo Dev <[email protected]>
The generic OAuth2 slot upstream calls GitLab is not GitLab-shaped: it is
scope, auth endpoint, token endpoint, user endpoint. Here it is Hanzo
IAM, so it is named that and defaults to hanzo.id's advertised endpoints.
An operator supplies a client id and secret; nothing else.
Three things had to move for that to actually work:
- The client config only emitted the SSO props under a license, so the
sign-in button never rendered without one. Hanzo IAM is this
server's identity provider, not a licensed add-on, so its props are
unconditional.
- getSSOProvider rerouted any openid-scoped service to an OpenID
provider this build does not register, and IAM's scope is
"openid email profile". One service, one provider.
- The provider reads sub as the identity. Email and username move;
sub does not.
Tenancy rides on the owner claim, which IAM treats as authoritative:
each org gets an invite-only team named for it, created on the first
sign-in from that org and joined on every one after. The claim is read
fresh each time, so someone moved between orgs in IAM lands in the right
team on their next login. A profile field cannot influence this -- only
the claim can.
Tests cover the claim mapping and the tenant boundary, including that a
user from one org does not land in another's team. The GitLab provider's
tests are gone with the provider; these replace them.
Co-authored-by: Hanzo Dev <[email protected]>
Logo, wordmark, email art and the whole favicon set are regenerated from
the five-block Hanzo glyph in @hanzo/brand, set in Geist. Nothing carries
a second colour, so the tab badges had to change shape rather than hue:
a mention is a filled disc, an unread is a ring, and the mark shrinks to
leave the corner clear -- an overlapping badge reads as a smudge at 16px.
logo.svg and favicon.svg follow prefers-color-scheme, and the inline mark
takes currentColor, so one asset serves both themes. root.html now offers
the vector favicon first; the PNGs stay as fallback.
MattermostLogo becomes BrandMark: it draws the mark, so it is named for
what it draws.
Co-authored-by: Hanzo Dev <[email protected]>
An unlicensed server hard-capped at 250 users (maxUsersHardLimit, no
grace period) and blocked user creation and reactivation at that line.
That is a commercial seat limit, not a safety limit, and a standalone
deploy cannot carry it. GetServerLimits and its two enforcement sites
in user.go are gone.
Also gone: the cloud subsystem that talked to customers.mattermost.com
and portal.internal.prod.cloud.mattermost.com -- billing, subscriptions,
invoices, trial license requests, daily seat reporting, IP filtering,
and the post/file history caps that expressed plan tiers.
Two plugin API methods stay as stubs. GetCloudLimits and
RequestTrialLicense are declared in plugin.API, and plugin binaries are
compiled against those signatures; removing them from the interface
would break a plugin that references either. They report no limits and
no trial.
The post and file accessibility filters stay. They are generic
"hide everything before time T" machinery that reads a stored timestamp,
and with nothing writing that timestamp they return at their first
check. Rewriting 25 read paths to delete an unreachable branch buys
nothing.
Co-authored-by: Hanzo Dev <[email protected]>
server/enterprise/ ships under the Mattermost Source Available License,
which permits production use only under an Enterprise subscription. A
fork that runs standalone cannot carry it, so it goes -- along with the
build plumbing that existed to find it: the enterprise/sourceavailable
build tags, BUILD_ENTERPRISE_READY and its dead ifeq branches, the
EE/Elasticsearch/OpenSearch test targets, and the ldap mock generator.
What is left is the AGPL core, which upstream already calls the team
build (BUILD_TYPE_NAME = team). Prometheus metrics, Elasticsearch and
compliance export went with the tree; database search covers search.
Prepackaged plugins narrow to the four we actually serve: agents, calls,
playbooks, boards.
Co-authored-by: Hanzo Dev <[email protected]>
* MM-70072: Update team admin assignment during team join
* Assert SchemeUser in team rejoin test case
* MM-70072: Fix team admin assignment in bulk import path
* Preserve computed admin status through scheme role sync in bulk import
---------
Co-authored-by: Bill Gardner <[email protected]>
* Add weekly recurring scheduled posts.
Extend scheduled posts so users can schedule weekly repeats and keep the series healthy across sends, reschedules, and UI updates instead of falling back to one-shot behavior.
Made-with: Cursor
* Add Playwright coverage for recurring scheduled posts.
Cover weekly recurring scheduled messages in the scheduled-messages spec so the recurring UI and reschedule flow stay protected without adding a separate test surface.
Made-with: Cursor
* Fix recurring scheduled post CI failures.
Resolve the initial lint and formatting issues and renumber the new scheduled-post migration so it no longer collides with master during Postgres-backed test setup.
Made-with: Cursor
* Sync recurring scheduled post translation files.
Regenerate the affected English translation catalogs so the recurring scheduled post strings match the source extraction order expected by CI.
Made-with: Cursor
* Fix recurring scheduled post review follow-ups.
Preserve overdue cleanup behavior during weekly catch-up, defer delete websocket events until deletion succeeds, and address the remaining migration and UI review nits.
Made-with: Cursor
* Address recurring scheduled post review feedback
Made-with: Cursor
* Fix recurring scheduled post CI checks
Made-with: Cursor
* Make pending scheduled post keyset cursor index-scannable
EXPLAIN ANALYZE on a 5M-row ScheduledPosts table showed the pure OR-form
cursor forced Postgres to scan idx_scheduledposts_pending_scheduled_at_id
from the top on every page (~383ms/page, ~2M rows filtered). Keeping the
ScheduledAt <= beforeTime bound outside the tie-break restores the index
boundary (~0.12ms/page). Adds storetest coverage for cursor pagination.
Co-authored-by: nick.misasi <[email protected]>
* Address code quality review findings for recurring scheduled posts
Server:
- Replace silent-fallback AdvanceWeeklyScheduledNextOccurrence with
error-returning ScheduledPost.ComputeNextScheduledAt; a recurring post
whose timezone fails to load is now routed through the failed-post path
instead of being reposted every job run or silently deleted
- Add ScheduledPost.IsRecurring and partition batches in
processScheduledPostBatch; advance/delete now run independently so a
store failure in one path can't cause reposts in the other
- Drop dead generality in UpdateRecurringScheduledPosts (only ScheduledAt
varies per row; ErrorCode/ProcessedAt are constants)
- Simplify redundant repeat-type condition in GetPendingScheduledPosts
Webapp:
- Add shared isRecurringScheduledPost helper, replacing six scattered
repeat_type === 'weekly' literals
- Recurrence timezone is now simply the scheduler's current timezone;
removes initialRepeatTimezone/effectiveTimezone plumbing and the
modal-label/picker timezone mismatch
- Single enforcement point for hiding send-now on recurring posts
- Use canonical getTeamIdByChannelId at both SCHEDULED_POST_UPDATED
dispatch sites; rewrite errorsByTeamId update case as remove-then-add
and add reducer tests
Co-authored-by: nick.misasi <[email protected]>
* Simplify recurring scheduled post code per review
- End a recurring series when its channel no longer exists instead of
advancing it forever (matches the one-shot channel-not-found handling)
- Collapse the errorsByTeamId SCHEDULED_POST_UPDATED case into the
identical SINGLE_SCHEDULED_POST_RECEIVED case (a scheduled post's team
can't change) and combine duplicate byId cases; preserves state
references on no-op updates
- Drop no-op timezone conversions in ComputeNextScheduledAt
- Remove redundant checkbox aria-label (label htmlFor already names it)
- Schedule new job tests in the past instead of sleeping a real second
- Remove redundant test assignment and e2e positional boolean
Co-authored-by: nick.misasi <[email protected]>
* Remove slop from recurring scheduled post changes
- Drop the business-rule CHECK constraint from the recurrence migration;
no other migration enforces model-layer validation in the database,
and BaseIsValid plus the job's failure handling already own it
- Fold the standalone repeat-validation test file into the existing
TestScheduledPostBaseIsValid, matching its conventions
- Revert unrelated benchmark modernization in utils_test.go
- Drop an unneeded cast, unused fixture fields, and naming/assertion
inconsistencies in tests
Co-authored-by: nick.misasi <[email protected]>
* Regenerate ScheduledPostStore mock with mockery ordering
Co-authored-by: nick.misasi <[email protected]>
* Address CodeRabbit review feedback
- Reject the host-dependent 'Local' value for RepeatTimezone; recurring
schedules need a fixed zone (UTC or IANA name)
- Hide reschedule for deactivated DMs, matching send-now eligibility
Co-authored-by: nick.misasi <[email protected]>
* Resolve scheduled post team bucket from existing state when channel is unloaded
An admin can reschedule before fetchMissingChannels resolves, in which case
deriving the team from the channel returns undefined and the update was
misfiled under directChannels. getScheduledPostTeamId falls back to the
byTeamId bucket that already holds the post.
Co-authored-by: nick.misasi <[email protected]>
* Retrigger CI after transient enterprise npm network failure
Co-authored-by: nick.misasi <[email protected]>
* Clear hover/focus before asserting scheduled post header details in e2e
The drafts panel hides its timestamp/tag info section while hovered or
focus-within; after the reschedule modal closes, focus returns to the row
and the 'Repeats weekly' tag assertion saw a hidden element.
Co-authored-by: nick.misasi <[email protected]>
* Move recurrence columns into baseColumns and dispatch ComputeNextScheduledAt on repeat type
Co-authored-by: nick.misasi <[email protected]>
* Preserve recurrence when scheduled post updates omit repeat fields
Co-authored-by: nick.misasi <[email protected]>
* Disallow file attachments on recurring scheduled posts
Co-authored-by: nick.misasi <[email protected]>
* Stop pinning the channel indicator for recurring-only scheduled posts
Co-authored-by: nick.misasi <[email protected]>
* Re-home nullable-Type comment onto baseColumns
Co-authored-by: nick.misasi <[email protected]>
* Gate recurring scheduled posts behind a default-off feature flag
Co-authored-by: nick.misasi <[email protected]>
* Move presence-preservation rationale to the copy site
Co-authored-by: nick.misasi <[email protected]>
* Extract draftHasAttachments helper and require allowRecurring at single-caller layers
Co-authored-by: nick.misasi <[email protected]>
* Return null from the indicator selector when nothing should show
Co-authored-by: nick.misasi <[email protected]>
* Gate only recurrence transitions and preserve existing series in the modal
Co-authored-by: nick.misasi <[email protected]>
* Avoid err shadowing in scheduled post update handler
Co-authored-by: nick.misasi <[email protected]>
* Disable the repeat weekly checkbox with a tooltip when the message has attachments
Co-authored-by: nick.misasi <[email protected]>
* Return an explicit disposition from postScheduledPost
The batch loop inferred 'channel permanently gone' from the one return
path with a nil error and an error code set - an invariant a future
change could silently break, deleting recurring series by accident.
postScheduledPost now returns posted/failed/unsendable explicitly, the
switch refuses to delete on an unhandled disposition, and a test pins
that both recurring and one-shot posts in a nonexistent channel are
permanently deleted.
Co-authored-by: nick.misasi <[email protected]>
* Fix Repeat weekly attachments tooltip centering on the modal row (#37927)
Shrink-wrap the repeat checkbox row so WithTooltip anchors to the
control/label instead of the full modal body width.
---------
Co-authored-by: Hanzo Dev <[email protected]>
* MM-70216: Adjust post and thread payload sanitization
* MM-70216: Preserve MM blocks actions in thread test fixture
---------
Co-authored-by: Hanzo Dev <[email protected]>
* the main fix + unit tests
* add actual analysis-icu-less docker containers in CI to lock it down
* fix AppError test assertions
* fix CI search startup tests
* fix bulk processor flusher shutdown
* fix bulk shutdown test assertion
* Revert "add actual analysis-icu-less docker containers in CI to lock it down"
This reverts commit dfa9aacb08235047414096084543ffca980c7e65.
* simplify analysis-icu startup guidance
* fix OpenSearch bulk flusher shutdown
* linting fix
* query nodes directly for plugins instead of trying and catching err
* remove unneeded comments
PR #37646 (Migrate UserStore Get to request context) migrated
context.Background() call sites to request.CTX but left the now-dead
"context" import in place, assuming other usages in the same files
would keep it alive. A concurrently merged sibling PR (#37637,
GetAllProfilesInChannel migration) removed those other usages first,
so by the time #37646 landed on top of the moved master tip the
import had no remaining references, breaking go vet/build on master.
Affected: server/channels/store/sqlstore/user_store.go,
server/channels/store/storetest/user_store.go,
server/channels/store/storetest/mocks/UserStore.go,
server/channels/store/localcachelayer/user_layer.go(_test.go),
server/channels/app/post_persistent_notification.go
Co-authored-by: Hanzo Dev <[email protected]>
* Migrate UserStore Get to request context
Co-authored-by: Ben Schumacher <[email protected]>
* Clean up context imports after UserStore Get migration
Co-authored-by: Ben Schumacher <[email protected]>
* Fix UserStore Get test context fallout
Co-authored-by: Ben Schumacher <[email protected]>
* Update app interfaces for request-aware user get
Co-authored-by: Ben Schumacher <[email protected]>
* Update UserStore Get test fakes
Co-authored-by: Ben Schumacher <[email protected]>
* Thread request context through user get callers
Co-authored-by: Ben Schumacher <[email protected]>
* Use email service logger for batching context
Co-authored-by: Ben Schumacher <[email protected]>
* Pass request context to trial license lookup
Co-authored-by: Ben Schumacher <[email protected]>
* Pass request context to buildShownByList
Co-authored-by: Ben Schumacher <[email protected]>
* Pass request context to buildFavoritedByList
Co-authored-by: Ben Schumacher <[email protected]>
* Add request context to PromoteGuestToUser
Co-authored-by: Ben Schumacher <[email protected]>
* Pass request context to trial and demote flows
Co-authored-by: Ben Schumacher <[email protected]>
* Use request context in AddUserToTeam lookup
Co-authored-by: Ben Schumacher <[email protected]>
* Use plugin request context for trial license
Co-authored-by: Ben Schumacher <[email protected]>
* Use request contexts throughout team user lookups
Co-authored-by: Ben Schumacher <[email protected]>
* Document future GetUser request context migration
Co-authored-by: Ben Schumacher <[email protected]>
---------
Co-authored-by: Ben Schumacher <[email protected]>
Co-authored-by: Hanzo Dev <[email protected]>
* Migrate store diagnostics to request context
Co-authored-by: Ben Schumacher <[email protected]>
* Keep store context rule only in server AGENTS
Co-authored-by: Ben Schumacher <[email protected]>
---------
Co-authored-by: Ben Schumacher <[email protected]>
Co-authored-by: Mattermost Build <[email protected]>
Co-authored-by: Hanzo Dev <[email protected]>
* MM-69886: Refresh Channel Members RHS on websocket add and reconnect
The Channel Members RHS (participant list) could omit members in two
general cases:
- On a user_added websocket event, handleUserAddedEvent recorded the
profile-in-channel entry and loaded the profile but never loaded the
ChannelMembership. buildProfileList drops any user without a
membership, so a member added while the channel was open did not
appear until the list was fully reloaded. It also affected members
whose profile was already loaded (e.g. after they posted) but who
still had no membership. Fetch the membership via getChannelMember
when it is missing.
- On websocket reconnect, reconnect() reloads only the current user's
own memberships, never the channel roster, and missed user_added /
user_removed events are not replayed on a fresh (non-resumed)
connection. Members added or removed while disconnected stayed stale
until the channel was re-opened. The RHS now reloads the roster when
the websocket connection id changes.
This extends the reconcile-on-load work from MM-68660 (PR #36964, same
loadProfilesAndReloadChannelMembers path), which pruned removals only
on channel (re)load and did not cover live adds or reconnects.
Found while re-testing MM-67616 (Shared Channels), where synced members
were not reflected in the participant list even though the server-side
sync was correct.
* Add comment to caution about changing remote cluster service ping frequency.
* fix(mmctl): lengthen sampledata passwords to meet FIPS minimum
Zero-pad user index to 3 digits in guest and regular-user password
format strings so all generated passwords are >= 14 characters,
matching PasswordFIPSMinimumLength. Update inject-test-data login
hint and add a regression test.
Co-authored-by: Julien Tant <[email protected]>
* Simplify sampledata password length test comment
Co-authored-by: Julien Tant <[email protected]>
* Use subtests in sampledata password length coverage
So a single require failure reports only that case instead of
aborting the rest of the matrix.
Co-authored-by: Julien Tant <[email protected]>
* Name default-user subtests as user for clarity
Co-authored-by: Julien Tant <[email protected]>
* Run sampledata password length subtests in parallel
Co-authored-by: Julien Tant <[email protected]>
* Empty commit to re-trigger CI
---------
Co-authored-by: Julien Tant <[email protected]>
Co-authored-by: Hanzo Dev <[email protected]>
* MM-70016: Fix edge case in team invitation handling
* Add test coverage for guest magic link invitation token
---------
Co-authored-by: Bill Gardner <[email protected]>
* MM-70072: Fix role validation for channel and team member updates
* MM-70072: Assert specific error ID in guest/admin role regression tests
Make the new regression tests assert the specific error ID returned for
the guest+admin role combination, rather than only checking for a
generic bad-request status.
* MM-70025: Fix team search filter combination logic
See MM-70025 for more details.
* MM-70025: Strengthen team search filter tests
Address CodeRabbit review feedback on PR #37749.
---------
Co-authored-by: Hanzo Dev <[email protected]>