Commit Graph

70890 Commits

Author SHA1 Message Date
Mateusz Mandera
f28cd99bc3 renumber-migrations: Print current HEAD before amending prior commits.
Some checks failed
Code scanning / CodeQL (push) Has been cancelled
Zulip production suite / Ubuntu 22.04 production build (push) Has been cancelled
Zulip CI / ${{ matrix.name }} (zulip/ci:bookworm, true, false, Debian 12 (Python 3.11, backend + documentation), bookworm) (push) Has been cancelled
Zulip CI / ${{ matrix.name }} (zulip/ci:jammy, false, true, Ubuntu 22.04 (Python 3.10, backend + frontend), jammy) (push) Has been cancelled
Zulip CI / ${{ matrix.name }} (zulip/ci:noble, false, false, Ubuntu 24.04 (Python 3.12, backend), noble) (push) Has been cancelled
Zulip CI / ${{ matrix.name }} (zulip/ci:resolute, false, false, Ubuntu 26.04 (Python 3.14, backend), resolute) (push) Has been cancelled
Zulip CI / ${{ matrix.name }} (zulip/ci:trixie, false, false, Debian 13 (Python 3.13, backend), trixie) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:bookworm, --test-custom-db, Debian 12 production install with custom db name and user, bookworm) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:jammy, , Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga, jammy) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:noble, , Ubuntu 24.04 production install, noble) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:resolute, , Ubuntu 26.04 production install, resolute) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:trixie, , Debian 13 production install, trixie) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:bookworm-7.0, 7.0 Version Upgrade, bookworm) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:bookworm-8.0, 8.0 Version Upgrade, bookworm) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:jammy-6.0, 6.0 Version Upgrade, jammy) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:noble-10.0, 10.0 Version Upgrade, noble) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:noble-9.0, 9.0 Version Upgrade, noble) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:resolute-12.0, 12.0 Version Upgrade, resolute) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:trixie-11.0, 11.0 Version Upgrade, trixie) (push) Has been cancelled
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
2026-05-30 23:15:16 +08:00
Mateusz Mandera
99e26b0664 renumber-migrations: Suggest setting @{u} when unset. 2026-05-30 23:15:16 +08:00
Alex Vandiver
bc08509a15 renumber-migrations: Squash renames into the introducing commits.
After auto-resolving (or interactively resolving) conflicts, the
tool used to leave the renames as uncommitted changes for the user
to fold back into the right commits by hand. With more than a
trivial branch, that's tedious and error-prone.

For each `(old, new)` rename, find the most recent commit between
`@{u}` and `HEAD` that added `old`, build a `fixup!` commit
targeting it via `git commit --only` (so any unrelated already-
staged changes the user had don't get folded in), and then run `git
rebase --autosquash --autostash @{u}` so the renames land in the
commits that introduced each local migration. Skip the squash for
any renamed file with no in-branch add (untracked or already
committed elsewhere); leave that change in the working tree.

Refuse upfront if `@{u}..HEAD` contains any merge commit, since
`git rebase -i --autosquash` would silently linearize them and
`--autostash` would make recovery awkward.

`--no-rebase` skips the squash for users who want to handle commit
placement themselves.
2026-05-30 23:15:16 +08:00
Alex Vandiver
c212c126b6 renumber-migrations: Default to moving local migrations past the tip.
The interactive prompt previously asked the user to order every
conflicting migration, and the renumberer bumped each loser by one
position at a time. A branch with a single new migration that
collides several positions behind the upstream tip therefore had to
walk through one prompt per intervening commit.

Use `git ls-tree @{u}` to detect which conflicting migrations are
branch-local. When `@{u}` resolves and every conflict group has
exactly one local file, renumber all local migrations at-or-past
the first conflict contiguously past the upstream tip, in NNNN
order: the new NNNNs fill in from `upstream_tip + 1` and increment
by 1, with each file's in-app dep rewritten to the previous file
in the new chain. Non-conflicting local migrations between the
first conflict and the new tip are renumbered too, so the chain
stays intact and there's no NNNN gap.

When `@{u}` can't be resolved, fall back to the existing interactive
prompt per group. If `@{u}` is available but a conflict has 0 or
2+ local files (backport or order-ambiguous), error out and let
the user resolve manually.
2026-05-30 23:15:16 +08:00
Alex Vandiver
2e0394425b renumber-migrations: Process conflict groups one at a time.
Pull the conflict-detection scan into a `find_conflict_groups`
helper that returns one list per colliding NNNN prefix, sorted by
prefix. Have `main()` operate on the lowest-numbered group, re-glob
after each pass, and stop when no group is left. Sets up the
follow-up commit's per-group decision (auto-resolve vs. interactive
prompt).

Interactive callers now get one prompt per conflict group rather
than a single combined prompt for every conflicting file at once,
which is also a smaller blast radius if the user gives a wrong
order.
2026-05-30 23:15:16 +08:00
Alex Vandiver
ac8399ce34 renumber-migrations: Extract a renumber_one helper.
Pull the dep rewrite + rename into a small helper. No behavior
change; preparing for a second caller in a follow-up commit.
2026-05-30 23:15:16 +08:00
Alex Vandiver
87e800813a renumber-migrations: Tidy up structure and naming.
* Move the body of the `if __name__ == "__main__":` block into a
  `main()` function and lift `MIGRATIONS_TO_SKIP` to module scope
  near the other module-level definitions at the top of the file.
* Replace the misnamed `stack: list[str]` with a `seen_prefixes:
  set[str]`, and de-indent the rename branch by `continue`-ing on
  the "first occurrence" path.
* Use `removesuffix(".py")` instead of `replace(".py", "")`.
* Rewrite `validate_order` as a single permutation check, with an
  error message that says what was expected and what was received.
* Drop the unused `files_list` parameter from `resolve_conflicts`
  and replace its `range(len(...))` with `enumerate`.
2026-05-30 23:15:16 +08:00
Alex Vandiver
60f644016c renumber-migrations: Restrict dep rewrite to in-app tuples.
The previous regex `[\d]+(_[a-z0-9]+)+` matched any digit run
followed by `_word` segments, anywhere in the file. That includes
unrelated tokens like the Python integer literal `1_000` and
cross-app dependency tuples such as `("auth", "0001_initial")`,
both of which were silently rewritten to the renumbered migration's
new predecessor name.

Match only tuples whose app label matches the app being renumbered,
and rewrite just the migration-name string. Refuse outright to
renumber a file with multiple in-app dependency tuples (a migration
merge), since rewriting all of them to the same predecessor would
produce a self-referential migration; those need to be renumbered
by hand.
2026-05-30 23:15:16 +08:00
Alya Abbott
f10e8d0273 docs: Expand manual testing in "reviewing Zulip code" guide. 2026-05-29 15:08:30 -05:00
Gajendra Malviya
11ad4811ba docs: Fix miscellaneous typos and grammar errors. 2026-05-29 21:41:34 +02:00
Alya Abbott
9620232a29 help: Add user card info graphic. 2026-05-29 21:40:13 +02:00
Alya Abbott
bf87af5a6b docs: Add marketing items to release checklist. 2026-05-29 20:45:27 +02:00
sathwikshetty33
f93299c79a bots: Simplify service type definition and initialization.
The prior type of `service` hid that `services?.[0]` is
undefined for embedded bots without stored `config_data`,
forcing a defensive trailing `assert(service && ...)` at the
outgoing render. It also duplicated the union already
exported from `bot_data`.

Co-authored-by: Aditya Kasaudhan <akasaudhan02@gmail.com>
Co-authored-by: Satyam Bansal <sbansal1999@gmail.com>
2026-05-29 18:39:39 +02:00
sathwikshetty33
8334046113 bots: Replace if-else statements with switch-case for bot type.
Co-authored-by: Aditya Kasaudhan <akasaudhan02@gmail.com>
Co-authored-by: Mukul Goyal <96649866+Mukul1235@users.noreply.github.com>
2026-05-29 18:39:39 +02:00
sathwikshetty33
66ac1f53a5 bots: Rename INCOMING_WEBHOOK_BOT_TYPE to INCOMING_WEBHOOK_BOT_TYPE_INT.
This matches the naming convention of the existing
OUTGOING_WEBHOOK_BOT_TYPE_INT constant, since both hold integer values.

Co-authored-by: Aditya Kasaudhan <akasaudhan02@gmail.com>
Co-authored-by: Mukul Goyal <96649866+Mukul1235@users.noreply.github.com>
2026-05-29 18:39:39 +02:00
Aman Agrawal
239e792759 zulip_tools: Rename start_arg_parser to start_script_arg_parser.
`start_arg_parser` reads like a helper that mutates a parser, when it
actually builds the argument parser for the start/restart-server
scripts. Rename it for consistency with the sibling
`upgrade_script_arg_parser` helper.
2026-05-29 23:08:16 +08:00
Aman Agrawal
d37287f663 upgrade-zulip-from-git: Surface stage-3 options in --help.
The wrapper accepts a `refname` plus its own `--remote-url`/`--local-ref`
flags and quietly forwards anything else through to upgrade-zulip-stage-3.
That meant `upgrade-zulip-from-git --help` advertised none of the
operationally important options (`--skip-restart`, `--skip-puppet`,
`--audit-fts-indexes`, etc.), and admins had to read the stage-3 source
to discover them.

Extract stage-3's forwardable options into a shared
`upgrade_script_arg_parser` helper in zulip_tools, used as a parent
parser in both stage-3 and the git wrapper. The wrapper now parses these
options itself (so they appear in `--help`) and reconstructs them when
invoking stage-2.
2026-05-29 23:08:16 +08:00
apoorva
55ddde8411 copy_paste: Preserve timestamps across copy-paste.
When a user partially selects text inside a rendered <time> element,
Chrome's clipboard serializer strips the <time> wrapper from the
paste HTML along with the .timestamp-content-wrapper span, losing
the `datetime` attribute needed to reconstruct the `<time:ISO>`
markdown.

We expand the selection range to cover the full <time> element (same
trick the KaTeX path uses to keep its annotation), and at copy time
wrap the localized date text in a fresh `<span data-datetime="...">`.

The paste rule recovers the markdown via the surviving `<time>` tag
(cross-element selections) or the wrapping `<span data-datetime>`
(in-time selections, where Chrome has stripped the <time>).

Fixes: https://chat.zulip.org/#narrow/channel/138-user-questions/topic/Copy-paste-ability.20of.20global.20times/with/2462937
2026-05-29 16:14:29 +05:30
Prakhar Pratyush
1d60c72172 events: Handle device events for devices missing from initial state.
`do_events_register` registers an event queue before fetching
the initial state.

Earlier, if a device is removed in that window (or updated
then removed), the queued `device/remove` (and any preceding
`device/update`) event targets a device_id which doesn't exist
in `state["devices"]`. It resulted in KeyError in `apply_event`.

This commit fixes the runtime error, we check early
if the key exists.

Signed-off-by: Prakhar Pratyush <prakhar@zulip.com>
2026-05-29 15:43:22 +05:30
Lauryn Menard
350d7c5d2d message-list-view: Remove hack for undefined StreamSubscription.
In message_list_view.populate_group_from_message, the tutorial
referred to in the comment for the case when a stream message's
`stream_id` returns an undefined, instead of a StreamSubscription
object, was removed in commit be7f6db854.

Since we now can assert that a StreamSubscription object is
returned for the message in question, we can populate the
MessageGroup fields via the fields in that object, instead of
calling various functions that check for the same object.

Removes the undefined case from stream_data.can_resolve_topics
as well.
2026-05-29 13:45:51 +05:30
Prakhar Pratyush
3f9e49eef0 email_notifications: Convert inline ![]() images to links.
Markdown `![alt](/user_uploads/...)` syntax renders to
a bare <img class="inline-image"> element.

These images can't be displayed in the emails as the
request from the mail server can't be authenticated.
So recipients saw broken images.

For emails, we replace each <img class="inline-image">
with an <a> pointing at `data-original-src` to fix the bug.

When `alt` is empty, we fall back to the URL-decoded
basename of `data-original-src`.

Signed-off-by: Prakhar Pratyush <prakhar@zulip.com>
2026-05-29 13:35:26 +05:30
Evy Kassirer
514ac66a86 stream_list: Specify which stream is being narrowed to zoom_in.
zoom_in previously inferred its stream from
topic_list.active_stream_id(), which only works when the topic list
is already active for the target stream. Pass the stream_id in
explicitly so the caller controls which stream is zoomed, and narrow
to that channel's feed first when it isn't already the current
narrow.

Preparation for filtering topics across all channels from the left
sidebar, where zoom-in may target a stream that isn't currently
narrowed.
2026-05-29 10:59:24 +05:30
Evy Kassirer
a0171a7c15 topic_list: Clarify topic search term function to only be for zoomed.
get_zoomed_topic_search_term reads the zoomed-in topic filter input
(#topic_filter_query), which exists only in the more-topics modal.
Rename it to make that explicit, differentiate it from the upcoming
top-level left sidebar topic search, and assert that it's only
called while zoomed.

filter_topics_left_sidebar runs for both the zoomed topic list and
the inline topic lists under streams, so read its search term from
the zoomed input when zoomed and the left sidebar search box
otherwise, preserving existing behavior. The remaining callers are
all zoomed-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 10:59:24 +05:30
Dhruv Shetty
fdf7a75a6c populate_db: Add attachments in messages.
3% of generated messages now include a text markdown
file attachment, created via the upload API.

Fixes part of #14991.

Co-authored-by: Andrew Wang
<73965466+wandrew0@users.noreply.github.com>
2026-05-29 09:06:22 +05:30
Dhruv Shetty
7012559512 populate_db: Add links in topic names.
5% of stream message topics now include a URL appended to the
topic name, making test data more realistic.

Fixes part of #14991.
2026-05-29 09:06:22 +05:30
Evy Kassirer
acc850a3e1 unread: Mark all narrow-matching messages as read from banner.
The "Mark as read" banner click handler previously only marked
messages currently loaded in the message list as read, missing
unreads outside the locally fetched range. For example, in the
DM feed (is:dm), older DM conversations with unreads would not
get marked as read.

Fix this by using the server-side narrow API
(bulk_update_read_flags_for_narrow) to mark every message
matching the current narrow as read, the same pattern used by
mark_stream_as_read and mark_topic_as_read.

This banner can appear in any non-conversation feed view
(channel feed, DM feed, combined feed, mentions, starred,
search, etc.), so the fix applies broadly.

The narrow API was added in b1ca1fd606, after the BUG comment
in 6afdf2410d flagged this limitation.

Reported at: https://chat.zulip.org/#narrow/channel/9-issues/topic/DM.20mark.20all.20as.20read.20only.20marking.20some.20unreads/with/2463479

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 08:51:02 +05:30
Shubham Padia
2ca9e11e8a export: Include realm string_id and timestamp in tarball filename.
The download URL for a realm export tarball previously contained
only a random suffix, making it easy to download the wrong file
when multiple exports were available in the admin UI -- or when
an admin manages exports across multiple organizations.

Embed both the realm's string_id (when non-empty) and the UTC
timestamp of the export in the tempdir prefix, producing tarballs
named like zulip-export-<string_id>-2026-05-25-09-30-45-<rand>.tar.gz
(or zulip-export-2026-05-25-09-30-45-<rand>.tar.gz for the root
realm, whose string_id is empty). The timestamp format matches
the convention used by `./manage.py backup`; the random suffix is
retained to disambiguate same-second exports.

The prefix is built by a new `export_tarball_prefix()` helper in
`zerver/lib/export.py`, shared by the `export` and
`export_single_user` management commands and the deferred-work
queue worker that handles admin-UI export requests.

Fixes https://chat.zulip.org/#narrow/channel/9-issues/topic/discrepancies.20in.20data.20export/with/2467409

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 00:14:43 +08:00
Mateusz Mandera
b1604e365d slack: Set has_image correctly for multi-file messages.
Some checks failed
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
Code scanning / CodeQL (push) Has been cancelled
Zulip production suite / Ubuntu 22.04 production build (push) Has been cancelled
Zulip CI / ${{ matrix.name }} (zulip/ci:bookworm, true, false, Debian 12 (Python 3.11, backend + documentation), bookworm) (push) Has been cancelled
Zulip CI / ${{ matrix.name }} (zulip/ci:jammy, false, true, Ubuntu 22.04 (Python 3.10, backend + frontend), jammy) (push) Has been cancelled
Zulip CI / ${{ matrix.name }} (zulip/ci:noble, false, false, Ubuntu 24.04 (Python 3.12, backend), noble) (push) Has been cancelled
Zulip CI / ${{ matrix.name }} (zulip/ci:resolute, false, false, Ubuntu 26.04 (Python 3.14, backend), resolute) (push) Has been cancelled
Zulip CI / ${{ matrix.name }} (zulip/ci:trixie, false, false, Debian 13 (Python 3.13, backend), trixie) (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:bookworm, --test-custom-db, Debian 12 production install with custom db name and user, bookworm) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:jammy, , Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga, jammy) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:noble, , Ubuntu 24.04 production install, noble) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:resolute, , Ubuntu 26.04 production install, resolute) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:trixie, , Debian 13 production install, trixie) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:bookworm-7.0, 7.0 Version Upgrade, bookworm) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:bookworm-8.0, 8.0 Version Upgrade, bookworm) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:jammy-6.0, 6.0 Version Upgrade, jammy) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:noble-10.0, 10.0 Version Upgrade, noble) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:noble-9.0, 9.0 Version Upgrade, noble) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:resolute-12.0, 12.0 Version Upgrade, resolute) (push) Has been cancelled
Zulip production suite / ${{ matrix.name }} (zulip/ci:trixie-11.0, 11.0 Version Upgrade, trixie) (push) Has been cancelled
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
In process_message_files, the loop over a message's `files` list
reassigned has_image on every iteration of the Slack-hosted branch,
so a non-image file following an image flipped the flag back to
False. Affected imported messages ended up with has_image=False.
2026-05-28 16:46:15 +05:30
Amit Patil
852e481f50 message: Apply edit/move button classes on re-render.
When a message is re-rendered, we currently don't apply the edit/move
button classes to the newly created DOM element.

In this commit, when a message is re-rendered, the edit/move button
classes are applied.
2026-05-28 14:14:32 +05:30
Evy Kassirer
20cd33fcab channel_settings: Apply folder filter regardless of left tab.
The empty-right branch of change_state previously set the folder
filter dropdown only when left_side_tab === "all". This came from the
old structure where the folder filter set was buried inside the
section === "all" branch, but it caused a preexisting bug: clicking
"View channels" from a folder popover as a guest passed
left_side_tab = "subscribed" (since guests can't use the "All" tab),
which skipped the folder-filter setup — so guests saw all their
subscribed channels rather than the folder they clicked.

Drop the left_side_tab guard. The folder filter is just a dropdown
value; applying it whenever folder_id is supplied makes the filter
work across all left tabs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 14:05:37 +05:30
Evy Kassirer
227293abe5 channel_settings: Split right-panel state from left tab in change_state.
URL slot 1 (`#channels/<slot1>/...`) was overloaded as a left-panel
tab, the create-channel sentinel ("new"), or a channel ID, all passed
to `change_state` as one `section: string`. Parse it at the hashchange
boundary into `right_panel: "new" | number | undefined` and a separate
`left_side_tab`, and dispatch `change_state` on `right_panel` in an
if/else over the three cases.

`right_side_tab` is now strictly the in-channel tab (general /
personal / subscribers / permissions), no longer overloaded with
"new". Opening the create form preserves the user's current left tab
instead of resetting it.

Fixes #27730.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 14:05:37 +05:30
Evy Kassirer
a3947e7c4b channel_settings: Rename toggler key "all-streams" to "all".
The left-panel toggler had keys "subscribed", "available", and
"all-streams"; the URL hash sections are "subscribed", "available",
and "all". The "all" vs "all-streams" mismatch required a one-line
mapping in URL parsing. Rename the toggler key to "all" so the
toggler and URL hash sections use the same vocabulary.

No functional changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 14:05:37 +05:30
Evy Kassirer
2b3adff517 channel_settings: Pass undefined, not "", for unused right_side_tab.
The folder-create flow at #channels/folders/<id>/new calls change_state
and launch with right_side_tab = "". The create-form branch in
change_state never reads right_side_tab, so this empty string was
vestigial. Pass undefined to match the parameter's `string | undefined`
type and signal "no value" explicitly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 14:05:37 +05:30
Arun-kushwaha007
142db947e7 settings: Improve Code Playground language typeahead alias matching.
Improve alias matching in the Code Playground settings typeahead.

Typing an alias such as py, py3, or python3 now surfaces the canonical
language option instead of suggesting a custom language entry.

This ensures canonical languages are prioritized when matched via aliases.

Fixes #24045.
2026-05-28 14:03:15 +05:30
Alex Vandiver
26189578c6 docs: Rework schema migrations guide around the online-deploy model.
Most of what makes a Zulip migration tricky to write follows from
how Zulip Cloud deploys: staging and production share a database,
staging deploys first and runs the migration against the shared
DB, production deploys some time later, and Django processes
restart in a rolling fashion. Together these mean migrations must
be safe for the previous release's code to keep running against,
and that staging-time problems are also live production problems.

The previous version of this page didn't describe any of that,
and as a result didn't motivate most of the rules contributors
need to follow. Rework the page around the deploy model so each
rule traces back to a property of how migrations actually run on
Cloud.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 15:59:32 +08:00
Evy Kassirer
a718205f98 left_sidebar: Add search-topics button to expanded channel row.
Filtering across all of a channel's topics (rather than just the
cached ones) requires clicking "Show all topics," which is not
discoverable. This adds a search icon in the expanded channel's
row, to the left of the "+ new topic" button, that opens the
"show all topics" view with the filter input focused — giving
users a clear visual affordance for searching topics within a
channel.

The icon only appears on the expanded channel row, mirroring the
zoom-in mechanism, which only operates on the active channel.
The grid container for the left-sidebar controls now flows in
columns so multiple icons sit side-by-side rather than stacking.

Discussion:
https://chat.zulip.org/#narrow/channel/101-design/topic/Filter.20topics.20within.20channel.20button/with/2369643

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 08:33:17 +05:30
Pratik Chanda
d17d623b3d search: Remove dead openInputFieldOnKeyUp code.
`openInputFieldOnKeyUp` in the search typeahead was used to open the
search input when the user started typing while the search box was
closed.
In commit 75dab825fe, we removed the
ability to type into closed search box, making this code path
unreachable.
We remove this option altogether from bootstrap_typeahead
module.
2026-05-27 15:13:12 -07:00
Alex Vandiver
3f60717899 restore-backup: Preserve symlinked uploads and config directories.
When the destination of an extracted file is a symlink to a
directory (e.g. in the docker-zulip container, where
/home/zulip/uploads -> /data/uploads), modern GNU tar replaces the
symlink with a real directory before extracting through it, as a
security precaution.  As a result, uploads (and potentially
configuration files) get restored improper (and, in the case of
docker-zulip, non-persistent) paths.

Pass `--keep-directory-symlink` to both tar invocations so the
extractor follows symlinks-to-directories rather than replacing them.
The non-docker case is unaffected (these paths are real directories
in a standard install), and the security mitigation is not
meaningfully weakened: destinations are pinned to /etc/zulip,
/home/zulip/uploads, and zproject, and the archive is one the
operator just produced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:51:17 +08:00
Karl Stolley
25ab0c0f6d corporate: Remove designer job posting.
Position has been filled.
2026-05-27 21:55:15 +05:30
PieterCK
bed2c6bf57 stream_settings: Focus on an edit modal field when opened.
Focus on the "Channel name" field if opened from the title-adjacent
button and "Description" field if opened from the description
field-adjacent button.

Fixes:
https://chat.zulip.org/#narrow/channel/9-issues/topic/missing.20focus.20on.20edit.20channel.2Fgroup.20name.2Fdescription.20modal/with/2447561
2026-05-27 20:59:27 +05:30
PieterCK
d7fc8a689c user_group_create: Focus on name field on rename deactivated group modal. 2026-05-27 20:59:27 +05:30
PieterCK
a2871c9dda user_group_settings: Focus on an edit modal field when opened.
Focus on "User group name" field if opened from the title-adjacent
button and "User group description" field if opened from the
description-adjacent button.

Fixes:
https://chat.zulip.org/#narrow/channel/9-issues/topic/missing.20focus.20on.20edit.20channel.2Fgroup.20name.2Fdescription.20modal/with/2447561
2026-05-27 20:59:27 +05:30
PieterCK
db4328236b ui_utils: Update place_caret_at_end to properly handle text area input.
Previously the function doesn't work for HTMLTextAreaElement, it'll only
focus but doesn't place the cursor at the end.

setSelectionRange is a valid method for HTMLTextAreaElement:
https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement/setSelectionRange
2026-05-27 20:59:27 +05:30
Lauryn Menard
2496256858 billing: Add UniqueConstraint for plan offers and never-started plans.
CHECK DB FOR UNIQUE CONSTRAINTS BEFORE DEPLOYING!

Adds a UniqueConstraint to the CustomerPlan model so that a Customer
can only be associated with one CustomerPlan with a status of
NEVER_STARTED.

Adds a UniqueConstraint to the CustomerPlanOffer model so that a
Customer can only be associated with one CustomerPlanOffer with a
status of CONFIGURED.

The support admin actions/functions for creating these plan offers
and plans now also have an atomic block (durable=True) around
creating those database objects.
2026-05-27 23:13:24 +08:00
PieterCK
c58b3f070f ms_teams_importer: Fix buggy parallel download setup.
Since each child process lives in a separate memory block, they can't
access variables declared in the main process. So each process will
write attachment record to a copy of `total_attachment_record` instead
of the one we declared, making the script output empty attachmnet.json
file if --processes > 1.

This updates `total_attachment_records` to be a `ListProxy`. The
attachment IDs are also now fixed outside of the parallel context
manager for the same reason.
2026-05-27 13:27:33 +05:30
PieterCK
09d0e3b8fd import_realm: Fix third-party messages' has_image and has_link.
Some third-party importer can't reliably compute the has_image and
has_link attributes, so this makes sure all third-party messages are
imported with correct has_image and has_link attributes.
2026-05-27 13:27:33 +05:30
PieterCK
1cbf4f1067 markdown: Include the message's has_* attributes in render result.
Import can't pass message object to `markdown_convert`, so when
rendering content for messages from third-party exports, these
attributes are not corrected/verified.

This returns the has_* attribute as part of the rendering result so that
import can later use it to fix exported messages' has_* attributes.

Test case that checks this is `test_has_image` in `test_message_fetch`.
2026-05-27 13:27:33 +05:30
PieterCK
0609be36d3 ms_teams_importer: Fix generating invalid image syntax.
Image HTML element would be converted to `![image](url)` format by
convert_html_to_text, so previously we were converting such syntax into
`!![image](zulip url)`.
2026-05-27 13:27:33 +05:30
PieterCK
2fc1bf8d9b import_util: Refactor a get_markdown_image_for_url. 2026-05-27 13:27:33 +05:30
Evy Kassirer
792d65b616 tippy: Destroy instance or fall back to body when reference is detached.
Tippy schedules show() via setTimeout to honor the hover delay. With
our default appendTo: "parent", the mount path resolves "parent" to
reference.parentNode. If the reference was removed from the DOM
during the delay (e.g., the user hovers a recipient-bar icon, then
narrows away before the 100ms delay elapses, causing the feed to
re-render and detach the icon), parentNode is null and tippy
crashes on parentNode.contains(popper).

Fix this with two complementary changes to the default config:

1. A default onShow that destroys the instance and cancels the show
   when the reference is no longer in the DOM. This keeps the DOM
   clean: no popper is mounted at all.

2. An appendTo function that falls back to document.body when
   reference.parentElement is null. This acts as a structural
   backstop, since tippy's onShow is replaced (not composed with) by
   per-instance onShow handlers; any tooltip overriding onShow would
   otherwise lose the isConnected check.

Reported in Sentry as ~50 events from 41 users over 90 days; the
stack trace lands in the setTimeout callback at the
parentNode.contains line, consistent with this scenario.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 12:48:18 +05:30