Skip to content

The size budget shortens the biggest rows first, gives back what fits, and never lists half the destroys

Decision record 0028

An issue body that is too large is dropped without an error (rendering research), and record 0024 fixed what must survive when a row shrinks. This record fixes the numbers, the steps and which rows give way. It was judged on a rendered fixture of 92 stacks whose full body was 335,856 characters and whose shortened body was 54,711.

The renderer aims at 58,000 characters, the headroom Renovate keeps under the same limit. A body over 65,536 characters or over 262,144 UTF-8 bytes is never written. Both are counted on the final body. The target leaves room for a late row swap by resolve, apply or settle to grow the body a little without going over.

A pending row has four levels:

  1. In full.
  2. The named pull requests on the attribution line are replaced by a count: from 3 pull requests, and 4 changes outside this stack · compare. The compare link stays (0026).
  3. The fold is replaced by one line, 48 other changes not listed here, see the summary. Every delete and replace line is still there.
  4. No change lines at all. A row with a delete or replace reads :warning: **deletes 451, too many to list here.** Read the [summary](url) before you tick. Any other row reads Changes not listed here, see the [summary](url).

Listing the first twenty deletes and then and 431 more was rejected. Which twenty a reader sees would be an accident of sort order, and a partial list can be taken for the whole. The delete and replace lines of a row are all there or none are, and then the warning carries the full count (0024). Taking the checkbox away from such a row was rejected too: a large teardown would then be impossible to deploy from the dashboard, while the summary shows it in full one click away.

Rows do not all drop to the same level. That was rejected because one stack with 450 changes would take the details away from 40 small rows. Instead, level by level, the biggest rows give way first until the body fits, and then small rows get their details back while there is room.

Consequences

  • The steps: render everything at level 0. While the body is over the target, go through levels 1, 2 and 3 in turn, and within a level move rows to it in order of rendered size, biggest first, until the body fits. Then go through the rows from smallest full size to biggest and give each the lowest level at which the body still fits. Ties are broken by stack id, so the same input gives the same body.
  • When any row is shortened, a top level note under the header says how many of the pending rows are shortened, that the summary shows every change, and that deletes and replaces are the last thing to be cut. A top level > [!NOTE] renders there, because it is outside any list.
  • A shortened row keeps its checkbox and its hash. The hash covers the whole diff whatever the row shows, the same safe direction as under redact (0023). The ticker approves the diff as the summary shows it.
  • The first line is never shortened, so the counts with their bold replaces and deletes survive every level, and so do the destroys and failed marker keys (0027).
  • Only pending rows are shortened. Every other kind of row is one to three lines. If the body is still over the hard limit with every pending row at level 3, the scan fails with a clear message and the old body stays. A level 3 row is about 550 characters, most of it links and the marker, and an in sync row about 110. So that point lies at roughly 100 stacks that are all pending at once. A further level that also drops links was left out of v1 (docs/later.md).
  • A writer that swaps rows (0004, 0011) has a diff only for its own rows. If its body comes out over the hard limit it shortens its own rows and never touches a carried row. If that is not enough it dispatches a scan, which can shorten everything.
  • A redacted dashboard (0023) has nothing to shorten. Its rows are already about the size of level 3.
  • The summary has no budget and is never shortened (0021). Amended by 0037: it has a budget of its own, far larger than the body’s, and the job log holds what does not fit.
  • The 58 stack fixture with two very large rows used 57 percent of the hard limit, so a dashboard of that size normally shows every row in full. Shortening is for the day a shared change makes everything pending at once (0010).

Settled while building (slice 1.8)

  • The row marker gains an optional key after failed: shortened="N", the level of a shortened row, left out for a row in full. It is a display cache like destroys and failed (0027) and changes nothing about the marker version (0009). The reason: the body outside the row blocks is regenerated by every writer (0009), and a writer that is not the scan may not read a row’s text, so it could not count the shortened rows and the note would vanish the first time resolve re-renders. Every writer now counts the pending rows whose marker holds the key. It holds the level and not true, because it costs fewer characters, the raw body then says why a row looks the way it does, and a later level needs no new key. The first line of a row is otherwise the same at every level.
  • The note names no single summary. A row that a narrowed scan carried through links to the summary of an earlier run, so the note reads: This dashboard is too large for one issue, so 39 of 53 pending rows are shortened. The summary that a shortened row links to shows every change. Deletes and replaces are the last thing to be cut. The rest is the wording of the prototype.
  • A higher level is not always smaller. A count can be longer than the one pull request it replaces, the warning of level 3 longer than one delete line, and the key above costs 14 characters. So a row moves to a level only when that makes the row smaller than it is now, and every step measures the whole body, the note included. Without this a body that cannot reach the target would hide a single delete line behind a longer warning.
  • Sizes are counted in UTF-16 units, as the write loop counts them. That is never fewer than GitHub’s characters, and 65,536 of them are at most 196,608 bytes, so the one number keeps a body inside both hard limits.
  • The target is where the budget aims and the hard limit is where it gives up. A body that is over 58,000 with every row cut as far as it goes and under 65,536 is written. A writer that swaps rows aims at the hard limit and not at the target, because the headroom between the two exists for exactly that writer.
  • A redacted dashboard can still give way at level 1, where the attribution line names pull requests. Levels 2 and 3 save nothing there and are never used.
  • On the 100 stack fixture (53 pending, 439,001 characters of rows in full) the body comes out at 57,687 characters: 14 rows in full, 36 at level 2, and the three rows with more than 450 deletes at level 3. No other row loses a delete or replace line.

Prototype: the over budget issue in the private lab repo, generated from the prototype/dashboard branch.

Settled while building (slice 3.1)

  • The scan mode, not only the renderer, holds these numbers (test/modes/hundred-stacks.test.ts). The 100 stack fixture scanned through the mode writes 55,983 characters, 38 rows shortened and the three rows with 450 deletes at level 3. It differs from the 57,687 above because the mode’s own links and attribution lines are not the prototype’s.
  • A level 3 row with its attribution line averages 547 characters, so “about 550” holds. With 21 character stack ids, 100 stacks that are all pending with deletes fit in 57,529 characters, 115 fit under the hard limit and 116 do not, and the scan then fails before it writes. So “roughly 100” holds too, on the safe side.