ui: Fold flamegraph trim alive-set computation into propagation scan

The previous trim pipeline ran a separate alive_set step that joined the
full merged table against propagated to derive a per-node alive flag.
On WASM-sized inputs (millions of merged rows) this was the single
slowest step in the trim pipeline (~58s vs ~2s native).

Have the propagation scan compute the alive flag inline as part of the
same expression that already evaluates the per-parent ratio + absolute
floor checks. The alive_set macro now becomes a one-line filter
(SELECT id WHERE alive) over the propagated table, with no JOIN. The
propagated index is no longer needed and is removed.

Net effect on native: alive step drops from 2.1s -> 0.06s on a 2.5M-row
bottom-up flamegraph. WASM should see the same kind of improvement
since the elimination is structural rather than micro-optimization.
diff --git a/src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql b/src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql
index e57d1b1..1d2a2f7 100644
--- a/src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/viz/flamegraph.sql
@@ -335,15 +335,20 @@
   GROUP BY hash
 );
 
--- Per-node propagation pass for the trim. For each node in |merged|,
--- emits |requiredCum|: the cumulativeValue that this node's CHILDREN must
--- clear in order to be kept. Roots emit 0. A node propagates +inf if it
--- itself failed the join thresholds (which kills its whole subtree, since
--- nothing is >= +inf).
+-- Per-node propagation pass for the trim. For each node in |merged| emits:
+--   alive       - whether the node itself survives the join thresholds.
+--   requiredCum - the cumulativeValue this node's CHILDREN must clear.
+-- Roots are always alive and emit requiredCum = 0. A node that itself
+-- failed the thresholds emits requiredCum = +inf, which kills its whole
+-- subtree on the next step (no cumulativeValue can be >= +inf).
+--
+-- Computing |alive| inside the scan rather than via a downstream JOIN of
+-- merged with propagated avoids an N x N pass over the merged tree, which
+-- on WASM-sized inputs (millions of rows) was the dominant cost.
 --
 -- Caller should materialize the result into a Perfetto table with an
--- index on |id|, since _viz_flamegraph_alive_set looks up parentId →
--- requiredCum once per merged row.
+-- index on |id|, since _viz_flamegraph_trim_with_placeholder looks up
+-- parentId -> alive while emitting placeholders.
 CREATE PERFETTO MACRO _viz_flamegraph_trim_propagation(
   merged TableOrSubquery,
   ratio Expr,
@@ -351,15 +356,18 @@
 )
 RETURNS TableOrSubquery
 AS (
-  SELECT id, requiredCum
+  SELECT id, requiredCum, alive
   FROM _graph_aggregating_scan!(
     (
       SELECT m.parentId AS source_node_id, m.id AS dest_node_id
       FROM $merged m
       WHERE m.parentId IS NOT NULL
     ),
-    (SELECT id, 0.0 AS requiredCum FROM $merged WHERE parentId IS NULL),
-    (requiredCum),
+    (
+      SELECT id, 0.0 AS requiredCum, TRUE AS alive
+      FROM $merged WHERE parentId IS NULL
+    ),
+    (requiredCum, alive),
     (
       SELECT
         x.id,
@@ -368,7 +376,9 @@
             AND t.cumulativeValue >= $min_value,
           $ratio * t.cumulativeValue,
           1e308
-        ) AS requiredCum
+        ) AS requiredCum,
+        (t.cumulativeValue >= x.incoming
+            AND t.cumulativeValue >= $min_value) AS alive
       FROM (
         SELECT id, MIN(requiredCum) AS incoming
         FROM $table
@@ -379,31 +389,13 @@
   )
 );
 
--- Given |merged| and the materialized + id-indexed |propagated| table from
--- _viz_flamegraph_trim_propagation, emits the ids of nodes that survive a
--- per-parent + absolute floor trim. A node is "alive" iff it is reachable
--- from a root through edges where every step satisfies BOTH:
---   child.cumulativeValue >= |ratio| * parent.cumulativeValue   (encoded
---                          in propagated[parent].requiredCum)
---   child.cumulativeValue >= |min_value|
--- Roots are always kept.
---
--- Caller should materialize the result with an index on |id| since
--- _viz_flamegraph_trim_with_placeholder looks it up three times.
+-- Returns the ids of nodes that survive the trim. Just a thin filter over
+-- |propagated| now that |alive| is computed inside the propagation scan.
 CREATE PERFETTO MACRO _viz_flamegraph_alive_set(
-  merged TableOrSubquery,
-  propagated TableOrSubquery,
-  min_value Expr
+  propagated TableOrSubquery
 )
 RETURNS TableOrSubquery
-AS (
-  SELECT m.id
-  FROM $merged m
-  LEFT JOIN $propagated pp ON pp.id = m.parentId
-  WHERE m.parentId IS NULL
-     OR (m.cumulativeValue >= pp.requiredCum
-         AND m.cumulativeValue >= $min_value)
-);
+AS (SELECT id FROM $propagated WHERE alive);
 
 -- Emits the trimmed flamegraph: kept rows from |merged| pass through, plus
 -- one synthetic '(merged)' placeholder per kept parent that has any
diff --git a/ui/src/components/query_flamegraph.ts b/ui/src/components/query_flamegraph.ts
index 03a28c0..0c7278c 100644
--- a/ui/src/components/query_flamegraph.ts
+++ b/ui/src/components/query_flamegraph.ts
@@ -492,8 +492,10 @@
   //   * an absolute floor derived from the root total (TRIM_FLOOR_DENOMINATOR)
   //     prunes long invisible chain tails - critical for chain-shaped data
   //     where the per-parent ratio alone cannot reduce row count.
-  // The pipeline is split into three materialized perfetto tables so
-  // each lookup hits an indexed table instead of an unindexed CTE.
+  // The propagation scan emits an `alive` flag per node so the alive set
+  // is a direct WHERE on propagated rather than a costly LEFT JOIN of the
+  // full merged table - this is the dominant cost on WASM for large
+  // bottom-up trees.
   const TRIM_RATIO_DENOMINATOR = 100;
   const TRIM_FLOOR_DENOMINATOR = 10000;
   const totalRow = await engine.query(`
@@ -518,22 +520,13 @@
     }),
   );
   disposable.use(
-    await createPerfettoIndex({
-      engine,
-      name: `_flamegraph_propagated_${uuid}_index`,
-      on: `_flamegraph_propagated_${uuid}(id)`,
-    }),
-  );
-  disposable.use(
     await createPerfettoTable({
       engine,
       name: `_flamegraph_alive_${uuid}`,
       as: `
         select *
         from _viz_flamegraph_alive_set!(
-          _flamegraph_merged_${uuid},
-          _flamegraph_propagated_${uuid},
-          ${trimMinValue}
+          _flamegraph_propagated_${uuid}
         )
       `,
     }),