Skip to content

console: add redesigned cluster details layout behind a UI preview - #39446

Merged
leedqin merged 2 commits into
mainfrom
cluster-page-layout
Oct 21, 2026
Merged

leedqin merged 2 commits into
mainfrom
cluster-page-layout

Conversation

@leedqin

@leedqin leedqin commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Part of CNS-173.

First PR in a stack that redesigns the cluster details page. The redesign puts the object lists under one tab, moves cluster configuration into a drawer, and replaces the overview charts with a combined resource usage view. This PR lands only the page shell, so each later PR adds one piece behind the same flag.

Description

  • The new page sits behind a new UI preview, clusterDetailsRedesign, offered by the LaunchDarkly flag cluster-details-page-cns-173. Users who are offered it opt in per browser from the pill in the breadcrumb bar ("Show the new cluster details page" / "Show classic cluster details page"). Self-managed keeps the classic page: it has no preview toggle and treats unlisted flags as on, so disabledFlexibleDeploymentFlags pins cluster-details-page-cns-173 off.
  • Tabs:
    • Metrics renders the existing ClusterOverview until the resource usage PR replaces it.
    • Objects shows the existing materialized view, index, source and sink lists behind a type switcher, at objects/:objectType.
    • Replicas is temporary. The configuration drawer PR moves replica management into the drawer and removes this tab.
  • A title row shows the cluster name with its size and replica count, or "Unmanaged" and "No replicas". It looks the cluster up itself, so a status change on any other cluster in the environment doesn't re-render the page. The configuration PR adds a gear here that opens the drawer.
  • Links work across both pages. Classic tab URLs (/indexes and the rest) redirect into the Objects tab, and objects/<type> URLs redirect to the matching classic tab, or to Overview for an unknown type. A bare objects link opens the first classic object tab.
  • ClusterDetailBreadcrumbs moves to its own module, and both pages render it from one shared header, which hosts the preview pill. Only the title row, tabs and routes switch under it, so the pill stays mounted and keeps keyboard focus when it switches pages.

Next in the stack: the resource usage data (#39507) and charts (#39508), then memory by object, worker skew, and the configuration drawer.

The cluster-details-page-cns-173 flag is on in LaunchDarkly's Staging environment, which PR previews read. Production needs it too before the preview can be offered there.

Verification

ClusterDetail.test.tsx adds tests for:

  • the preview switch in both directions, including opting in from a classic tab URL, with keyboard focus staying on the pill;
  • the new title and tabs, including an unmanaged cluster with no replicas;
  • the Objects type switcher;
  • the redirects in both directions, including an unknown object type;
  • keeping the current tab when switching clusters from the breadcrumb menu.

useUiPreview.test.tsx adds a test that self-managed keeps the redesign off.

🤖 Generated with Claude Code

@leedqin
leedqin force-pushed the cluster-page-layout branch from 0d3a2b7 to 9748a34 Compare October 2, 2026 23:17
@leedqin
leedqin added this pull request to stack #39509 October 2, 2026 23:17
@leedqin
leedqin force-pushed the cluster-page-layout branch from 9748a34 to da596f5 Compare October 5, 2026 15:19
@leedqin
leedqin marked this pull request as ready for review October 5, 2026 15:20
@leedqin
leedqin requested a review from a team as a code owner October 5, 2026 15:20
@leedqin
leedqin requested a review from jdonelson October 5, 2026 15:20
@leedqin
leedqin force-pushed the cluster-page-layout branch 2 times, most recently from 86b3a41 to 3ebaa38 Compare October 7, 2026 02:24
<Route path="sources" element={<Sources key={clusterName} />} />
<Route path="sinks" element={<Sinks key={clusterName} />} />
<Route
path="objects/:objectType"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new page's Objects tab links to bare objects, but the classic page only matches objects/:objectType. So a copied Objects tab link opens an empty body for someone who's opted out. Worth adding an objects route here that redirects too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The flag is on for everyone that is why I linked it here. Will make a note to remove Maintained Objects flag

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. The classic page now has an objects route that opens the first object tab, with a test.

@leedqin
leedqin force-pushed the cluster-page-layout branch 3 times, most recently from bb11a98 to 2e4d2c8 Compare October 12, 2026 23:36
@leedqin
leedqin force-pushed the cluster-page-layout branch 6 times, most recently from be135e0 to f8a6ef1 Compare October 15, 2026 21:55
@leedqin
leedqin force-pushed the cluster-page-layout branch 8 times, most recently from a4b8ed0 to c3ab4f4 Compare October 20, 2026 18:32
@linear-code

linear-code Bot commented Oct 20, 2026

Copy link
Copy Markdown

CNS-173

First of a stack that redesigns the cluster details page. It sits behind
the clusterDetailsRedesign UI preview (LaunchDarkly flag
cluster-details-page-cns-173), which users opt into per browser from the
page header. Without the opt-in the classic page is unchanged.

The new page adds a title row with the cluster's size and replica count,
and three tabs: Metrics (the existing overview for now), Objects (the
existing materialized view, index, source and sink lists under one tab)
and Replicas. Replicas is temporary until a configuration drawer manages
replicas.

Classic tab URLs redirect into the Objects tab, and objects/ URLs
redirect to the classic tabs, so shared links work for both groups. The
breadcrumbs move to their own module so both pages share them.
Self-managed deployments read flags from flexibleDeploymentFlags, where a
flag that's absent counts as on, and they have no preview toggle. Without
an entry, self-managed users would get the unfinished redesign with no way
to opt out. Pin cluster-details-page-cns-173 to false there.

Also switch the new page's relative imports to `~/` and spell the object
type label "Materialized Views", matching the classic tabs.
@leedqin
leedqin force-pushed the cluster-page-layout branch from c3ab4f4 to f74dd3d Compare October 20, 2026 19:39
@leedqin
leedqin merged commit f7e2199 into main Oct 21, 2026
83 checks passed
@leedqin
leedqin deleted the cluster-page-layout branch October 21, 2026 15:03
leedqin added a commit that referenced this pull request Oct 21, 2026
…39507)

### Motivation

Second PR in the cluster details redesign stack, on top of #39446. It
adds the data behind the resource usage card: memory split into RAM and
swap, replica status over time, hydration, heap limits and object
creation times. The card itself is the next PR (#39508), so this one can
be reviewed without the chart code. Nothing renders these queries yet.

Part of
[CNS-174](https://linear.app/materializeinc/issue/CNS-174/show-cluster-memory-swap-metrics).

### Description

New queries. Each filters on literal replica or object ids, so it plans
as an index lookup on `mz_catalog_server`, except where noted.

| Data | Source | Refresh |
|---|---|---|
| RAM and swap as shares of the heap limit | `swap_of_ram_percent` and
`heap_limit_percent` on the console utilization views, 26.44 and later.
Windows past 14 days use the ad-hoc query, which now computes both from
`mz_cluster_replica_metrics_history` the way the views do. | with the
utilization data |
| Replica status transitions, and out-of-memory kills on any process |
`mz_cluster_replica_status_history`: process 0's rows, which carry a
replica's status, plus any `oom-killed` row, since an OOM is recorded
only on the process it killed | when any of the replicas' processes
changes status: the newest `updated_at` among the replica statuses that
the app-wide clusters subscribe already carries, so the query doesn't
poll. The cluster's own `latestStatusUpdate` can't serve, since it comes
from `mz_cluster_replica_history`, which leaves out system clusters. |
| Finished hydrations | `mz_replica_hydration_history`, 26.43 and later,
where it gained `process_id`. No index, so it only runs for replicas
that came back online inside the window. | 60s |
| How many objects each current replica has yet to hydrate |
`mz_compute_hydration_times` | 30s |
| How long each current replica took to hydrate each index and
materialized view created in the window, null while it still is |
`mz_compute_hydration_times` | 30s |
| Heap limit per replica | `mz_cluster_replica_metrics` | 60s, until
every replica reports a heap limit, or reports memory with no limit, as
the process orchestrator always does |
| Creation times of the cluster's indexes and materialized views |
`mz_object_lifetimes`, filtered to those object types since other kinds
share the `u<N>` id form | once per set of ids |
| A replica's per-process samples, for the Processes drawer in #39508
and the workers drawer in #39889 | `mz_cluster_replica_metrics_history`,
a lookup on its `replica_id` index | live SUBSCRIBE while the drawer is
open |
| Each size's process count and per-process CPU, memory and disk
allocation, for the same drawers | `mz_cluster_replica_sizes`, fast path
on its index | once |

**Per-process reads.** The resource usage card charts replicas, not
processes. This PR adds the per-process subscribe builder and the sizes
query that the Processes drawer in #39508 and the workers drawer in
#39889 read. The subscribe starts at a literal time one window back, and
`useRollingWindowStartMs` moves that start forward once per window, so
an open subscribe holds at most two windows of samples. A `mz_now()`
temporal filter would instead hold each sample's pending retraction in
the subscribe on `mz_catalog_server`: on a local emulator with 2
replicas of 16 processes over 24h, that measured ~12 MiB per open
subscribe, against ~2 MiB with the literal bound. EXPLAIN on
`mz_catalog_server`: a lookup on
`mz_cluster_replica_metrics_history_ind`, with the bound as a plain
filter. Per-process catalog views are tracked in
[CNS-203](https://linear.app/materializeinc/issue/CNS-203).
`useUtilizationTier` holds the tier choice (3h subscribe, 24h subscribe,
or poll) that the replica-level paths share.

`useReplicaUtilizationHistory` gets two options:
- `includeMemoryBreakdown` adds the RAM and swap columns. Callers that
don't pass it keep their query keys, so the classic page sends the same
queries as before.
- `includeOfflineEvents: false` skips the 20s offline-events poll on
windows of 3h or less. The new page reads offline stretches from status
history instead.

`useClusterObjectsLive` (in `allObjectsCollection.ts`) returns a
cluster's objects of the given types as a TanStack DB live query over
the existing `allObjectsCollection`. Filtering `useAllObjects` gives a
new array on every DDL anywhere in the environment; the live query keeps
its result until one of this cluster's matching objects changes. It
reuses the app-wide objects subscribe, so it adds no catalog_server
load.

`resourceUsageModel.ts` holds the chart logic as pure functions: status
timelines, replica colors, memory bar segments, and merging buckets so
bars stay readable on wide windows. Every merged group spans the same
interval, clipped to the window, so replicas that start or stop
mid-group still line up side by side.

The k6 cluster detail replay lists these queries' refresh intervals,
since it replays unlisted queries every 5s. The testing guide's
scalability commands now use `yarn test:e2e:scalability`, because the
`chromium` project skips `scalability/`.

`types/materialize.d.ts` is edited by hand, with TODOs on the new
columns, because `yarn gen:types` fails against current builds
(`pg_get_serial_sequence` doesn't exist).

A replica's hydration in progress has no recorded start, so the timeline
model doesn't draw one. The next PR shows it as a count of objects still
hydrating at the now edge instead.

`mz_replica_hydration_history` only records a replica's hydration after
it starts, so an object created on a running replica left no stretch.
`paintObjectHydration` draws those from the per-object times. `time_ns`
counts from when the replica installed the object's dataflow, so a
stretch starts at the later of the object's creation and the replica's
last start, and one still hydrating runs to the end of that run. Objects
created before environmentd last started (`now() - mz_uptime()`, read in
the same request) are left out: an upgrade, or a restart under the
process orchestrator, puts every replica on new processes that rehydrate
every object without a status-history row, so their times can't be
placed.

### Verification

- `replicaUtilizationBinning.test.ts`: RAM and swap shares survive
binning and come from the bucket's highest-heap sample.
- `allObjectsCollection.test.ts`: `useClusterObjectsLive` returns the
cluster's objects of the given types, keeps its result through changes
on other clusters, and updates when one of its objects changes.
- `resourceUsageModel.test.ts`: status timelines (state before the
window, readings before the first transition once older history is gone,
clipping, out-of-memory markers, hydration stretches), replica colors,
memory bar segments, the RAM limit, and bucket merging.
- `replicaProcessMetrics.test.ts`: the per-process subscribe bounds its
samples with a literal start and no temporal filter.
- `replicaProcessMetrics.test.sql.ts`, against a real Materialize:
- the subscribe, read through a cursor over stand-in samples, returns
only the given replicas' samples from the start, with process ids as
text and a failed process's row kept with nulls;
  - it also runs against the catalog's own metrics history;
- the sizes query matches `mz_cluster_replica_sizes` for the listed
sizes.
- `replicaStatusTimeline.test.ts`: status transitions come from process
0, and OOM kills from any process.
- `resourceUsageModel.test.ts` also covers groups that start at
different buckets, clipping to the window, a merged width capped at the
window, and RAM limits that disagree between sizes that swap and sizes
that don't.
- `queries.test.ts` also covers: the newest status change across
replicas and processes, system replicas included; the status history
query refetching only when it moves, and keeping its rows meanwhile; the
heap-limit poll stopping and continuing; and the rolling start being set
on the first render.
- `resourceUsageModel.test.ts`: `paintObjectHydration` anchors a stretch
at the object's creation or the replica's last start, runs an unfinished
one to the end of the run, and paints only running time.
- `queries.test.ts`: `useObjectHydrationStretches` groups rows by
replica, leaves out objects created before environmentd started, and
stays idle without objects; past 14 days, the memory breakdown reaches
the ad-hoc query on 26.44 and later only.
- `replicaUtilizationHistory.test.sql.ts`: past 14 days, the ad-hoc
query returns RAM and swap shares from each bucket's highest-heap
sample, and none without the option.
- `replicaStatusTimeline.test.sql.ts`: the hydration times query filters
to the given replicas and objects, converts to milliseconds and keeps
nulls, and reads a real index's hydration; environmentd's start lands
before an index created during the test.
- `queries.test.ts`: `useRollingWindowStartMs` starts one window back,
moves once per window, never returns a start for another window, and
takes a fresh start when a window comes back.
- The plans in the table were checked with EXPLAIN on
`mz_catalog_server` in a local v26.45 build, and the two per-process
queries on a cloud environment.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@bosconi bosconi mentioned this pull request Oct 23, 2026
@leedqin leedqin added the A-CONSOLE Area: Console label Oct 28, 2026
val-materialize added a commit that referenced this pull request Oct 30, 2026
Release notes for the **v26.48.0** train. One commit is added per
snapshot; the `final` snapshot sets the real dates. **Applied: `rc.1`,
`rc.2`, `rc.3`, `final` — the train is complete.**

- **Content**: 3 Feature sections (upsert hydration; Kafka sink headers
GA; MySQL `EXCLUDE ALL NOT NULL CONSTRAINTS`), 9 Improvements, 2 Guides,
4 Bug Fixes. No **Agent Skills** section — every agent-skills PR in all
four windows was omitted.
- **Scope**: 123 PRs across the four snapshots — rc.1 114 (14 included),
rc.2 5 (0 included), rc.3 1 (1 included), final 3 (0 included).
- **Dates — now real, not provisional**: Cloud **2026-10-29**,
Self-Managed **2026-10-30**. The `rc.1`-anchored provisional estimate
was right, so `final` left both files unchanged. The `v26.48.0` tag's
commit date is 2026-10-28T18:35:05Z, a Wednesday; the first Thursday on
or after it is 2026-10-29, with Self-Managed the day after.
- **Operator compatibility**: row for `v26.48`, release date
`2026-10-30`. Unchanged by rc.2, rc.3, and final.
- **Branch base**: `main`, whose newest published section was `##
v26.46.1` when this branch was cut; v26.47.0 and v26.47.1 have since
merged in. `v26.48.0` stays at the top of the list.

### final increment — empty, and verified empty

`final`'s commit is empty. The tracking issue
([release#627](MaterializeInc/release#627))
lists exactly one item, the unassociated `4edd60a8d` "release: bump to
version v26.48.0", which the release-process-automation rule omits. The
three agent-skills PRs in the window
([#118](MaterializeInc/agent-skills#118),
[#119](MaterializeInc/agent-skills#119),
[#120](MaterializeInc/agent-skills#120)) were
all omitted.

This was **verified rather than assumed**, because a zero-PR snapshot
and a failed fetch produce identical output:
`compare/v26.48.0-rc.3...v26.48.0` returns `total_commits: 1`, and that
commit is the version bump. Nothing merged onto the release branch in
the 42 h 49 m between the `rc.3` tag and the ship tag. Across the train,
every agent-skills PR from
[#111](MaterializeInc/agent-skills#111) to
[#120](MaterializeInc/agent-skills#120) falls in
exactly one window, with no gaps or overlaps.

⚠️ **One decision is still open for the release owner — see "Upsert v2"
below.** Every flag this train held open was re-read in source at the
`v26.48.0` ship tag rather than inherited from the RC snapshots:

| Flag | Default at `v26.48.0` | Source | Held since | Disposition |
| -- | -- | -- | -- | -- |
| `enable_upsert_v2` | **`false`** |
`src/storage-types/src/dyncfgs.rs:505-509` | rc.2 | ⚠️ premise
contradicted — see below |
| `enable_upsert_chunked_stash` | **`false`** |
`src/storage-types/src/dyncfgs.rs:469-475` | rc.2 | same |
| `enable_upsert_paged_spill` | **`false`** |
`src/storage-types/src/dyncfgs.rs:450-455` | rc.2 | same |
| `enable_mcp_protocol_2026_07_28` | **`false`** |
`src/adapter-dyncfgs/src/dyncfgs.rs:295-299` | rc.1 | hold stands —
still omitted |
| `cluster-details-page-cns-173` | n/a (LaunchDarkly) | no source
default | rc.1 | hold stands — still omitted |

**The rc.2 hold on
[#40189](#40189) rests
on a premise this branch now contradicts.** rc.2 omitted it at
Borderline on exactly one ground — every measurement is taken on a path
`enable_upsert_v2` keeps off by default — and asked whether any
environment actually runs upsert v2. The branch answers that without
anyone feeding the answer back: the human-added **"Improved hydration
times and memory efficiency for upsert sources"** section states *"This
change has been rolled out to all Materialize Cloud environments and
applies to all existing and new clusters."* The three flags still read
`false` at the ship tag, which is exactly the
source-default-versus-LaunchDarkly divergence the rollout-lever rule
covers: the published docs call wins, and the sole stated reason for the
omit has fallen away.

- **Not already covered.** The published section is about **hydration**
— building a source from scratch, 206 min → 108–113 min, 542 GiB →
183–208 GiB. #40189 is about **catch-up after a backlog**, a different
operation with its own motivating incident
([CPU-320](https://linear.app/materializeinc/issue/CPU-320): a source
that *"could not catch up on any replica size after a producer rate
increase"*). A customer who read the hydration section would learn
nothing about catch-up.
- **Suggested Improvements bullet, to add only if you confirm** — it was
*not* applied in the `final` commit:
> **Faster catch-up for upsert sources with a large backlog**: An upsert
source working through a backlog now drains one timestamp per round
instead of merging and re-pushing the whole backlog each round, so
catch-up time grows linearly rather than quadratically with the backlog;
draining a 100-timestamp tail fell from 6.5 to 0.15 seconds, and a
400-timestamp tail drains in 0.67 seconds.
- **Caveat that must travel with the decision**: the baseline in those
timings is the *paged* flavor on `main`, not the configuration Cloud ran
before this rollout — so they are not a clean before/after for a given
customer. Dropping the two timings and keeping the
linear-versus-quadratic claim holds regardless of baseline.
- **Two weaker links, stated rather than buried**: that the rolled-out
configuration is the one #40189 was measured on is *inferred from the
published section's prose* (its "buffer pool … serve sequential work
from disk" reads as chunked stash plus paged spill), not confirmed by
anyone. And the author declared "Release notes: None", naming
`enable_upsert_v2` — so publishing this would be an **override** of an
author signal, justified by your own later rollout statement.
- **Third consecutive train** to hold this flag: `v26.44.0-rc.1`
([#39041](#39041)),
`v26.45.0-rc.1`
([#39044](#39044)),
`v26.48.0-rc.2` (#40189). If the rollout statement is accurate, the
earlier two are worth revisiting rather than left to age out.

**The other two holds stand, corroborated.**
`enable_mcp_protocol_2026_07_28` still reads `false` at the ship tag and
the five MCP PRs rc.1 held appear nowhere in the section; neither does
the Console cluster-details page stack. In both cases you had the whole
train in view and did not promote them — the confirmation rc.1 asked
for, arriving in the negative.

**No duplicates, re-checked at ship.** The seven PRs rc.1 flagged as
already covered by v26.47.0 are still absent, the `## v26.47.1` section
is untouched, and rc.3's entry
([#40065](#40065))
appears exactly once.

**Two notes on human edits to this branch**, both recorded as fact
rather than objection:

- `524adb8` removed the rc.1 feature section **"Native Google Cloud
Storage for persist"**
([#39983](#39983))
outright. Flagging it only because a published entry disappearing is
easy to miss in a long branch — if that was deliberate, nothing to do.
- Two rc.1 Improvements were promoted to their own feature sections with
docs links and a code sample (Kafka sink headers
[#39908](#39908);
MySQL `EXCLUDE ALL NOT NULL CONSTRAINTS`
[#39873](#39873)), and
a **Guides** section was added that is not in the skill's template. The
lesson for next time: an option whose flag flipped to `true` in the same
release more likely warrants a feature section than the Improvements
bullet the rubric suggests by default.

**Carried forward from rc.3 and still open**: the `K8s recovery: envd on
failing node` nightly — the job that sank
[#40029](#40029), the
first attempt at what #40065 re-lands — **was not checked**; it needs
Buildkite, outside this run. The entry itself was verified correct in
source at both the rc.3 and ship tags, so this is a CI-confidence
question, not a notes-accuracy one.

### rc.3 increment

One Improvement, for #40065: a read-only `environmentd` that reboots to
pick up DDL changes during zero-downtime upgrade catch-up now restarts
in place rather than exiting its container, so Kubernetes no longer
applies crash-restart backoff. Not inherited from rc.1 — rc.1 correctly
omitted the first attempt (#40029) together with its revert (#40061) as
a no-op on `main`; rc.3 re-lands the behavior in narrowed form, verified
as a real change by comparing `entrypoint.sh` at the `v26.47.0` tag (no
retry loop) against `v26.48.0-rc.3` (re-exec on exit 75). The draft's
closing "so zero-downtime upgrades catch up faster" was cut: no cited PR
measures an upgrade duration, so the entry claims only the mechanism.

- **Not a Bug Fix.** 0dt upgrades completed on v26.47.0; the catch-up
reboots merely collected backoff delay. Existing behavior improved, so
Improvements.
- **Residual, deliberately unpublished.** A stale read-only process in a
replaced pod still restarts in place on these halts until the kubelet
kills its container, competing with its replacement for the generation's
replicas. The `MZ_RESTARTED_IN_PLACE` marker check keeps it from booting
writable, so it cannot fence the leader — the failure #40061 reverted.
Worth watching if 0dt reports look odd after this ships.

### rc.2 increment — empty, by design

rc.2's commit is empty: no entry was published and the provisional dates
did not move. It exists so later runs can see from the commit subject
that rc.2 was applied. Its single materialize PR, #40189, is the open
item discussed under the `final` increment above.

### rc.1 — standing warnings

⚠️ **Seven PRs in rc.1's tracking issue were already covered by the
v26.47.0 train, and none is republished here.** v26.47.0 took five RCs
whose later ones were cherry-picks onto the release branch; the same PRs
appear in this issue because they are also what landed on `main` for
v26.48.0. Four of the seven **shipped to Cloud in v26.47.0 on
2026-10-22** — #37653, #39910, #39927, #40036 — so republishing any of
them would put duplicate text in front of customers eight days later.
The other three (#39903, #39906, #40046) were held at Borderline in
v26.47.0 and are held again here, each reason re-checked rather than
inherited. The "snapshots are disjoint by construction" guarantee holds
*within* a train, not across trains, so this was caught mechanically by
grepping every PR number in the issue against the v26.47.0 snapshot
directories. All seven are listed under their own heading in
`rc.1/omitted.md`.

**Open questions for the release owner**

- **Upsert v2** (#40189, rc.2) — see the `final` increment above. The
rollout statement on this branch has changed the picture since rc.2;
this is the one item that needs a decision before merge.
- **MCP protocol revision 2026-07-28** (#39698, #39702, #39703, #39759,
#39761) — the stack is complete and `enable_mcp_protocol_2026_07_28`
still defaults to **`false`** at the ship tag. Omitted, with the absence
of any MCP text on this branch taken as confirmation. Drafted wording
remains in `rc.1/omitted.md` if the flag flips.
- **Redesigned cluster details page in the Console** (#39446, #39507,
#39508, #39510, #39607) — omitted for the same reason. Note that the
Improvements section does carry **"New cluster details page on
Self-Managed"**, added by hand, which links the v26.47 section rather
than re-describing the page.
- **Schema projection on the Console's Freshness page** (#40031) — a
genuine new way to diagnose lag, but the author states the whole
Freshness page "is currently behind a feature flag that is only enabled
for certain customers in impersonation mode". **Is the page generally
reachable yet?** This is the same page v26.47.0 held for the same
reason.
- **Time zone data 2026e timing** (#40045) — the author flags the
Canadian zone changes as effective **2026-11-01**, three days after the
Cloud date. Tight, but it holds.

*Resolved since rc.1: the Kafka sink headers framing question — the GA
section on this branch answers it.*

**Verification notes** — rc.1's Step 5 pass cut one drafted entry and
rewrote four:

- **Cut**: a Bug Fix drafted for #40032 ("halt with exit code 168 when a
live reader loses its lease"). The PR's own Problem section is about
alerting — `mz_persist_lease_timeout_read` cannot distinguish a dead
reader from a stuck one — and the follow-up it names is a Cloud alerting
change. Nothing a customer observes changes.
- **tzdata** (#40045): the draft said the Canadian zones move to
"permanent daylight saving time", the author's prose. The PR's data
table shows a permanent **−05/EST** and **−06/CST** — summer *offsets*
kept year-round, but with another zone's standard-time *abbreviations*,
which is what a user sees from `AT TIME ZONE`. Reworded to what the
table supports.
- **Hydration memory** (#39923): scoped to the consolidation step and
the measured outcome (~628 GiB peak, no restart, against every
pre-change run exiting on the 849 GiB limit). The PR explicitly says the
fix "covers this consolidation path, not the sink's whole footprint";
[CPU-317](https://linear.app/materializeinc/issue/CPU-317) tracks the
rest.
- **Kafka clients** (#40026): "55 to 24" is measured on a 4-worker,
11-source setup. The CPU throttling and degraded freshness are observed
symptoms of the *old* behavior at a few hundred sources, not a measured
post-change result at that scale, so the entry states the count as the
outcome and the throttling as what the count drove.
- **`environmentd` accept backlog** (#40035): the author's hedge is kept
— "less likely to be restarted", not eliminated, because the PR says
"This makes the queue fill later, not never."

**Judgment calls a reviewer may want to revisit** — #39605 is filed as
an **Improvement**, not a Bug Fix: nothing was incorrect, an unbounded
operation gained a bound and a knob. #40101 is filed as a **Bug Fix**
rather than omitted under the internal-logging rule, because credentials
reaching the logs is a security defect with a user-visible remedy; only
credentials a user inlined rather than wrapped in a `SECRET` were
affected.

Snapshot drafts (with PR links) in mz-skills:
- rc.1:
[release_notes.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/rc.1/release_notes.md)
/
[omitted.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/rc.1/omitted.md)
- rc.2:
[release_notes.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/rc.2/release_notes.md)
/
[omitted.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/rc.2/omitted.md)
- rc.3:
[release_notes.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/rc.3/release_notes.md)
/
[omitted.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/rc.3/omitted.md)
- final:
[release_notes.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/final/release_notes.md)
/
[omitted.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/final/omitted.md)

**Borderline PRs omitted:** rc.1: 12 —
[omitted.md#borderline](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/rc.1/omitted.md#borderline),
rc.2: 1 —
[omitted.md#borderline](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/rc.2/omitted.md#borderline),
rc.3: 0, final: 0

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Valerie Chiang <[email protected]>
Co-authored-by: Pranshu Maheshwari <[email protected]>
Co-authored-by: Pranshu Maheshwari <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-CONSOLE Area: Console

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants