@wafertools/wafermap 0.30.1 → 0.30.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +71 -11
- package/CHANGELOG.md +464 -8
- package/README.md +6 -3
- package/dist/packages/canvas-adapter/chartPopulation.d.ts +48 -0
- package/dist/packages/canvas-adapter/chartPopulation.js +1 -0
- package/dist/packages/canvas-adapter/charts/barPanel.d.ts +4 -1
- package/dist/packages/canvas-adapter/charts/barPanel.js +1 -1
- package/dist/packages/canvas-adapter/charts/boxplot.d.ts +4 -1
- package/dist/packages/canvas-adapter/charts/boxplot.js +1 -1
- package/dist/packages/canvas-adapter/charts/capability.js +1 -1
- package/dist/packages/canvas-adapter/charts/chartShell.d.ts +81 -7
- package/dist/packages/canvas-adapter/charts/chartShell.js +1 -1
- package/dist/packages/canvas-adapter/charts/correlation.js +2 -2
- package/dist/packages/canvas-adapter/charts/groupedBarPlot.js +1 -1
- package/dist/packages/canvas-adapter/charts/histogram.js +1 -1
- package/dist/packages/canvas-adapter/charts/regionYieldDiagram.js +1 -1
- package/dist/packages/canvas-adapter/charts/scatter.js +1 -1
- package/dist/packages/canvas-adapter/charts/sweep.d.ts +31 -0
- package/dist/packages/canvas-adapter/charts/sweep.js +1 -0
- package/dist/packages/canvas-adapter/charts/trend.d.ts +3 -1
- package/dist/packages/canvas-adapter/charts/trend.js +1 -1
- package/dist/packages/canvas-adapter/chunked.d.ts +45 -0
- package/dist/packages/canvas-adapter/chunked.js +1 -0
- package/dist/packages/canvas-adapter/dieList.js +2 -2
- package/dist/packages/canvas-adapter/drilldown.d.ts +23 -0
- package/dist/packages/canvas-adapter/drilldown.js +1 -0
- package/dist/packages/canvas-adapter/icons.js +1 -1
- package/dist/packages/canvas-adapter/insightsTab.d.ts +55 -3
- package/dist/packages/canvas-adapter/insightsTab.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferGallery.d.ts +55 -0
- package/dist/packages/canvas-adapter/renderWaferGallery.js +1 -1
- package/dist/packages/canvas-adapter/renderWaferMap.js +1 -1
- package/dist/packages/canvas-adapter/summaryPanel.d.ts +55 -9
- package/dist/packages/canvas-adapter/summaryPanel.js +3 -3
- package/dist/packages/canvas-adapter/toCanvas.js +1 -1
- package/dist/packages/canvas-adapter/toolbar.d.ts +30 -2
- package/dist/packages/canvas-adapter/toolbar.js +2 -2
- package/dist/packages/canvas-adapter/userGuideHtml.d.ts +1 -1
- package/dist/packages/canvas-adapter/userGuideHtml.js +126 -3
- package/dist/packages/canvas-adapter/version.d.ts +2 -2
- package/dist/packages/canvas-adapter/version.js +1 -1
- package/dist/packages/canvas-adapter/warnings.js +1 -1
- package/dist/packages/core/aggregates.js +1 -1
- package/dist/packages/core/utils.d.ts +55 -0
- package/dist/packages/core/utils.js +1 -1
- package/dist/packages/renderer/axisTicks.d.ts +32 -0
- package/dist/packages/renderer/axisTicks.js +1 -0
- package/dist/packages/renderer/binColors.d.ts +14 -0
- package/dist/packages/renderer/binColors.js +1 -1
- package/dist/packages/renderer/buildView.d.ts +11 -37
- package/dist/packages/renderer/buildView.js +1 -1
- package/dist/packages/renderer/buildWaferMap.d.ts +58 -1
- package/dist/packages/renderer/buildWaferMap.js +1 -1
- package/dist/packages/renderer/deprecate.d.ts +13 -5
- package/dist/packages/renderer/deprecate.js +1 -1
- package/dist/packages/renderer/derivedTests/apply.d.ts +85 -0
- package/dist/packages/renderer/derivedTests/apply.js +1 -0
- package/dist/packages/renderer/derivedTests/evaluate.d.ts +48 -0
- package/dist/packages/renderer/derivedTests/evaluate.js +1 -0
- package/dist/packages/renderer/derivedTests/parser.d.ts +148 -0
- package/dist/packages/renderer/derivedTests/parser.js +2 -0
- package/dist/packages/renderer/fmt.d.ts +24 -2
- package/dist/packages/renderer/fmt.js +1 -1
- package/dist/packages/renderer/index.d.ts +1 -0
- package/dist/packages/renderer/index.js +1 -1
- package/dist/packages/renderer/spec.d.ts +49 -0
- package/dist/packages/renderer/spec.js +1 -0
- package/dist/packages/renderer/testLabel.d.ts +154 -0
- package/dist/packages/renderer/testLabel.js +1 -0
- package/dist/packages/stats/analyzeWaferMap.js +1 -1
- package/dist/packages/stats/capability.d.ts +60 -0
- package/dist/packages/stats/capability.js +1 -1
- package/dist/packages/stats/correlation.d.ts +33 -0
- package/dist/packages/stats/correlation.js +1 -1
- package/dist/packages/stats/deprecated.d.ts +3 -0
- package/dist/packages/stats/deprecated.js +1 -1
- package/dist/packages/stats/findingsNarrative.js +1 -1
- package/dist/packages/stats/index.d.ts +2 -2
- package/dist/packages/stats/index.js +1 -1
- package/dist/packages/stats/math.d.ts +27 -2
- package/dist/packages/stats/math.js +1 -1
- package/dist/packages/stats/mergeTestDefs.js +1 -1
- package/dist/packages/stats/renderFindingsReport.js +8 -28
- package/dist/packages/stats/renderSummaryReport.js +22 -31
- package/dist/packages/stats/reportHtml.d.ts +27 -0
- package/dist/packages/stats/reportHtml.js +35 -14
- package/dist/packages/stats/sweep.d.ts +178 -0
- package/dist/packages/stats/sweep.js +1 -0
- package/dist/packages/stats/sweepXFromName.d.ts +27 -0
- package/dist/packages/stats/sweepXFromName.js +1 -0
- package/dist/packages/stats/testPassRate.js +1 -1
- package/dist/packages/stats/types.d.ts +28 -1
- package/llms.txt +14 -8
- package/package.json +2 -2
package/AGENTS.md
CHANGED
|
@@ -25,8 +25,24 @@ loudly over guessing.
|
|
|
25
25
|
- `@wafertools/wafermap/worker` — `createWafermapWorker()` for off-main-thread builds.
|
|
26
26
|
|
|
27
27
|
Default path: `buildWaferMap()` once when data loads, then `renderWaferMap()` for a
|
|
28
|
-
single wafer or `renderWaferGallery()` for several.
|
|
29
|
-
|
|
28
|
+
single wafer or `renderWaferGallery()` for several.
|
|
29
|
+
|
|
30
|
+
**The types still export a large deprecated surface that is removed in 0.31.0 — do not
|
|
31
|
+
reach into it just because autocomplete offers it.** Four groups, all replaced by the
|
|
32
|
+
default path above:
|
|
33
|
+
|
|
34
|
+
- the low-level drawing pipeline — `buildView()`, `toCanvas()`, `createWafer()`,
|
|
35
|
+
`generateDies()`, and the geometry and transform helpers around them;
|
|
36
|
+
- the chart-data builders — `buildYieldData()`, `buildCorrelationMatrix()`,
|
|
37
|
+
`buildTestBoxplotData()` and the rest: these were the internals of the Insights tab,
|
|
38
|
+
which the renderers now mount for you (see below);
|
|
39
|
+
- the region builders — `buildRingRegions()`, `buildQuadrantRegions()` and friends:
|
|
40
|
+
region yield comes back from `analyzeWaferMap()`;
|
|
41
|
+
- per-die and per-colour helpers — `getDieTestValue()`, `buildHoverText()`,
|
|
42
|
+
`resolveBinColors()`, `getValueColorScheme()`, `valueToViridis()`.
|
|
43
|
+
|
|
44
|
+
If the only way to do something is through one of these, that is a library gap worth
|
|
45
|
+
reporting, not a pattern to build on.
|
|
30
46
|
|
|
31
47
|
### Traps that produce silently wrong maps
|
|
32
48
|
|
|
@@ -37,8 +53,20 @@ or the other low-level pipeline functions: they are deprecated and removed in 0.
|
|
|
37
53
|
through unchanged; `dieConfig.width`/`height` convert to physical units. Do not
|
|
38
54
|
pre-multiply. The geometry inputs are `waferConfig` (type `WaferConfig`) and
|
|
39
55
|
`dieConfig` (type `DieConfig`) — both optional, both inferred when omitted.
|
|
40
|
-
-
|
|
41
|
-
|
|
56
|
+
- **Bins and test values must be numbers, and verdicts booleans.** Every parser —
|
|
57
|
+
CSV, JSON, a spreadsheet export — hands you `"1"`, and `"1"` is not pass bin 1: those
|
|
58
|
+
dies count as fails and the yield is wrong, while a test value left as text is not
|
|
59
|
+
plotted or analysed correctly. Convert with `Number()` at parse time. `buildWaferMap`
|
|
60
|
+
samples the input and reports `input-values-not-numbers` (severity `'error'`) rather
|
|
61
|
+
than coercing behind your back, so a build that "works" can still be wrong — read the
|
|
62
|
+
warnings.
|
|
63
|
+
- **`passBins` and `ringCount` are set once, on `buildWaferMap`, and travel on the
|
|
64
|
+
result.** Neither is an option on `analyzeWaferMap`, `analyzeWaferLot`,
|
|
65
|
+
`renderWaferMap` or `renderWaferGallery` — passing one there is a type error in
|
|
66
|
+
TypeScript, and in JavaScript it is ignored with an `analysis-option-corrected`
|
|
67
|
+
warning while the real value is read from the map. `passBins` decides both the yield
|
|
68
|
+
number and the wording of its label, so set it from the actual test program: do not
|
|
69
|
+
assume `[1]`, and never re-default to `[1]` downstream — read `result.passBins`.
|
|
42
70
|
- **`testValues` is keyed by test number**, e.g. `{ 1050: 0.42 }` — not a positional
|
|
43
71
|
array. `activeTest` likewise takes a *test number* (`1050`), not an index.
|
|
44
72
|
- **Functional tests (`testType: 'F'`) have no measured value.** Read their verdicts
|
|
@@ -88,7 +116,37 @@ or the other low-level pipeline functions: they are deprecated and removed in 0.
|
|
|
88
116
|
your own (a table swatch, an export)? Read `controller.getBinColors()` for a live map,
|
|
89
117
|
or `binColorsForMaps(results)` — never a palette lookup of your own.
|
|
90
118
|
- Build once, render many: `buildWaferMap()` handles data + geometry; re-render UI
|
|
91
|
-
changes through the controller's `setOptions()`, not by rebuilding.
|
|
119
|
+
changes through the controller's `setOptions()`, not by rebuilding. New data for a
|
|
120
|
+
map that is already mounted goes through `setResult()` — do not `destroy()` and
|
|
121
|
+
remount.
|
|
122
|
+
- **The analysis surfaces are already built — do not reimplement them.** Pass
|
|
123
|
+
`statsSummary` to `renderWaferMap` and it mounts the Summary panel; pass
|
|
124
|
+
`insights: { enabled: true }` and it mounts the chart suite (yield, bin pareto,
|
|
125
|
+
boxplot, histogram, correlation, scatter, capability, and one card per
|
|
126
|
+
`insights.sweeps` entry). `renderWaferGallery` takes the same option across a whole
|
|
127
|
+
lot. Supply or replace the analysis later with `setStatsSummary()`. Hand-building
|
|
128
|
+
those charts is what the deprecated chart-data builders were for, and they go in
|
|
129
|
+
0.31.0. Charting a selection or one wafer (right-click → histogram, capability,
|
|
130
|
+
sweeps) is built in too, with no wiring.
|
|
131
|
+
- **A value computed from other tests is a derived test, not a host-side column.** Pass
|
|
132
|
+
`derivedTests` (a `TestDef` plus an `expression`, e.g. `'abs(t[1020] - t[1010])'`) to
|
|
133
|
+
`buildWaferMap`; it then behaves as a measured test everywhere, marked `†` as not
|
|
134
|
+
measured. Computing it in the host and injecting it into `testValues` loses that
|
|
135
|
+
mark, the missing-input rule (absent, never 0) and the collision check. A boolean
|
|
136
|
+
expression must be declared `testType: 'F'`.
|
|
137
|
+
- **A sweep's x values are data, not guesses.** Give `xValues`, or `xFromName` (a
|
|
138
|
+
`{x}` placeholder pattern, not a regex) when the swept value is only in the test
|
|
139
|
+
name. Never derive x from test numbers: they are identifiers, not a scale.
|
|
140
|
+
- **Click-to-highlight is wired, not hand-rolled.** `onSelect` reports what the user
|
|
141
|
+
picked; `setSelection(dies)` / `clearSelection()` drive it from your own UI — for
|
|
142
|
+
example from a finding, whose `dieKeys` match `getDieKey(die)` exactly.
|
|
143
|
+
- A die layout with no test data — a map of the reticle or the grid alone — is
|
|
144
|
+
`buildWaferMap({ layout: true, waferConfig, dieConfig })`, not a synthesized results
|
|
145
|
+
array.
|
|
146
|
+
- `valueColorScheme` and `reverseValueScheme` travel as a pair. Every built-in gradient
|
|
147
|
+
but `'traffic'` and `'jet'` reads low = dark, high = light; if you draw your own
|
|
148
|
+
colorbar or swatch, resolve the colour through `resolveValueColorFn(name, reversed)`
|
|
149
|
+
so it cannot disagree with the dies.
|
|
92
150
|
- `result.view` is internal. Use the promoted fields: `result.plotMode`,
|
|
93
151
|
`result.metadata`, `result.isLotStack`, `result.hbinDefs`, `result.sbinDefs`,
|
|
94
152
|
`result.testDefs`.
|
|
@@ -135,10 +193,10 @@ it is handed, because it has no way to know which tests anyone will look at.
|
|
|
135
193
|
- **A Web Worker buys responsiveness, not speed.** `createWafermapWorker` copies data
|
|
136
194
|
across `postMessage`, so total time goes *up*. Use it when a build would otherwise
|
|
137
195
|
visibly freeze the page, not for small datasets.
|
|
138
|
-
- **Capability, pass rates
|
|
139
|
-
`stats.capability` (with `computePerTestStats`), `stats.testSpecYield`,
|
|
140
|
-
`stats.testFlagYield`, `stats.functionalYield
|
|
141
|
-
lot summaries alike. Do not compute Cp/Cpk or ring yield yourself: the pooled
|
|
196
|
+
- **Capability, pass rates, region yield and the spatial-pattern label come back from
|
|
197
|
+
the analysis** — `stats.capability` (with `computePerTestStats`), `stats.testSpecYield`,
|
|
198
|
+
`stats.testFlagYield`, `stats.functionalYield`, `stats.regionYield` and
|
|
199
|
+
`stats.spatialPattern`, on wafer and lot summaries alike. Do not compute Cp/Cpk or ring yield yourself: the pooled
|
|
142
200
|
within-wafer stddev and per-wafer pass bins are easy to get subtly wrong.
|
|
143
201
|
- **Reports from code: `renderWaferReportHtml(result, summary)` and
|
|
144
202
|
`renderLotReportHtml(results)`** — they take the built maps, so pass bins and ring
|
|
@@ -157,7 +215,7 @@ it is handed, because it has no way to know which tests anyone will look at.
|
|
|
157
215
|
| `WaferMapResult.inference.warnings` | `WaferMapResult.warnings` (structured, with a `code`) |
|
|
158
216
|
| `ViewOptions.testIndex` | `activeTest` |
|
|
159
217
|
| `mountWaferCanvas` | `renderWaferMap` |
|
|
160
|
-
| `HARD_BIN_COLORS` / `SOFT_BIN_COLORS` | `
|
|
218
|
+
| `HARD_BIN_COLORS` / `SOFT_BIN_COLORS` | `BinDef.color`, or `registerBinColorScheme` — there is no exported palette constant |
|
|
161
219
|
| `GalleryItem` | `WaferMapDisplayItem` |
|
|
162
220
|
| `MountOptions` | `RenderOptions` |
|
|
163
221
|
| `WaferCanvasController` | `WaferMapController` |
|
|
@@ -172,6 +230,8 @@ it is handed, because it has no way to know which tests anyone will look at.
|
|
|
172
230
|
| `plotMode: 'specLimit'` | `passFailDisplay: 'spec'` |
|
|
173
231
|
| standalone `getDieAtPoint` | `onHover` / `onClick` on `renderWaferMap` |
|
|
174
232
|
| `RenderOptions.tooltipTestLimit` | (was a no-op; nothing replaces it) |
|
|
233
|
+
| `enableYieldAnalysis` / `enableHardBinAnalysis` / `enableSoftBinAnalysis` / `enableReticlePositionAnalysis` / `enableTestSiteAnalysis` / `enableClusterAnalysis` / `enableAngularAnalysis` / `enablePatternClassification` | nothing — every analysis runs; scope cost with `testNumbers` instead |
|
|
234
|
+
| `WaferMapController.setIdentityVisible` | `showIdentity` in `RenderOptions` |
|
|
175
235
|
|
|
176
236
|
Passing a removed option is a type error, and is ignored at runtime. Do not add
|
|
177
237
|
compatibility shims for them.
|
|
@@ -191,7 +251,7 @@ compatibility shims for them.
|
|
|
191
251
|
- [API reference](https://wafertools.github.io/wafermap/api/) — every type, option and return value
|
|
192
252
|
- [Developer guide](https://wafertools.github.io/wafermap/guide/) — worked walkthroughs
|
|
193
253
|
- [Troubleshooting](https://wafertools.github.io/wafermap/troubleshooting/)
|
|
194
|
-
- [Examples](https://wafertools.github.io/wafermap/examples/) —
|
|
254
|
+
- [Examples](https://wafertools.github.io/wafermap/examples/) — 22 runnable pages, also
|
|
195
255
|
[downloadable](https://wafertools.github.io/wafermap/wafermap-examples.zip) to run offline
|
|
196
256
|
|
|
197
257
|
When a rule here and the API reference disagree, the API reference wins — tell the
|
package/CHANGELOG.md
CHANGED
|
@@ -22,18 +22,474 @@ under `### Breaking`.
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
## [0.30.3] — 2026-09-23
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- **Sweep x values from test names, an x unit, and a log x axis.** For a
|
|
30
|
+
program that records the swept quantity only in the test text — a
|
|
31
|
+
resistance CDF named `Normalized_LRS= LRS_STATS_12K / …`, one test per
|
|
32
|
+
threshold:
|
|
33
|
+
- `SweepSeriesSpec.xFromName` reads each test's x from its name with a
|
|
34
|
+
placeholder pattern, `"LRS_STATS_{x}"`: `{x}` reads a number with its SI
|
|
35
|
+
prefix (`12K` → 12,000; case-sensitive, `K` accepted as kilo; a letter is a
|
|
36
|
+
prefix only alone or before a unit, so `12Kangaroos` reads 12; in an all-capitals
|
|
37
|
+
name the unit's case is gone, so `5NS` is 5 ns and `2MV` — milli or mega? — is
|
|
38
|
+
reported, not guessed), `*` matches
|
|
39
|
+
anything, literal text matches in any case, anywhere in the name. Not a
|
|
40
|
+
regular expression: a sweeps file is shared, and JavaScript cannot
|
|
41
|
+
interrupt a runaway regex, whereas this matcher's cost is bounded whatever
|
|
42
|
+
the pattern (`stats/sweepXFromName.ts`). Each x stays attached to its own
|
|
43
|
+
test, so a missing test loses one point instead of shifting the rest.
|
|
44
|
+
- `SweepSpec.xUnit` prints the x axis, crossing and widths SI-prefixed
|
|
45
|
+
(`47.3 kΩ`), trailing zeros dropped (`2 kΩ`, not `2.00 kΩ`).
|
|
46
|
+
- `SweepSpec.xScale: 'log'` places x on a log axis; the crossing is
|
|
47
|
+
interpolated along log x and a width is reported as a ratio with both ends
|
|
48
|
+
(`×2.49 (15.9 kΩ → 39.7 kΩ)`), via new `SweepSeparation.ratio`/`from`/`to`.
|
|
49
|
+
`SweepData.xScale` is the axis actually drawn: a non-positive x keeps it
|
|
50
|
+
linear, with a warning.
|
|
51
|
+
- Reported, never guessed, and then not measured: a name the pattern does not
|
|
52
|
+
fit, a series giving both `xValues` and `xFromName`, and — for every x
|
|
53
|
+
source — a series that revisits an x value, which the crossing previously
|
|
54
|
+
mis-paired silently.
|
|
55
|
+
|
|
56
|
+
- **Drilldown: chart a selection or a wafer.** Right-click a population for a
|
|
57
|
+
menu of charts drawn from just those dies, opened in the same modal as an
|
|
58
|
+
expanded Insights card:
|
|
59
|
+
- **Populations:** the dies selected on a map (a single map or any gallery
|
|
60
|
+
card); a whole wafer, from empty map space with nothing selected, from a
|
|
61
|
+
gallery card anywhere outside its map, or from one wafer's bar, box or point
|
|
62
|
+
in *Yield by wafer*, *Test value distribution* or *Wafer-to-wafer trend* in
|
|
63
|
+
Insights (their tooltips say "right-click to chart this wafer"). The Menu
|
|
64
|
+
key, Shift+F10 and a new map toolbar **Chart** button (the selection if
|
|
65
|
+
there is one, else the wafer) reach the same menu without a mouse.
|
|
66
|
+
- **Charts:** value histogram (opening on the test the map shows), process
|
|
67
|
+
capability, and the sweeps defined in `insights.sweeps` — the gallery now
|
|
68
|
+
passes those to each card, and a single map offers them whether or not
|
|
69
|
+
`insights.enabled` is set.
|
|
70
|
+
- **Population stated on every chart**, in the modal title and on the card
|
|
71
|
+
("12 dies selected on W03", "10 of 12 … (partial and edge-excluded dies left
|
|
72
|
+
out)"). Capability below 30 dies adds that each Ppk is a rough estimate.
|
|
73
|
+
Charts are snapshots: changing the selection afterwards does not change them.
|
|
74
|
+
The wafer is named by the item's `label`, else its `metadata.waferId` — never a
|
|
75
|
+
positional "Wafer 3 (no ID)" — on the map, the gallery and Insights alike.
|
|
76
|
+
- **The library decides what is offered:** a chart that cannot be drawn stays
|
|
77
|
+
listed, greyed, with the reason (no values for its tests; fewer than two
|
|
78
|
+
dies for capability; a lot-stack map, whose dies are per-position aggregates
|
|
79
|
+
rather than measured dies). Right-clicking an unselected die selects it
|
|
80
|
+
first. A map with no parametric tests and no sweeps leaves right-click to
|
|
81
|
+
the browser or host, and the map canvas owns right-click on itself: one it
|
|
82
|
+
declines (the bin legend) never reaches a gallery card or host handler.
|
|
83
|
+
- Documented in api.md §5.12, the user guide §4.4 (with screenshots from
|
|
84
|
+
`capture-definitions.mjs`) and the developer guide.
|
|
85
|
+
- Built on one source/target mechanism (`canvas-adapter/chartPopulation.ts`
|
|
86
|
+
builds the populations, `drilldown.ts` the menu and charts, loaded on first
|
|
87
|
+
use and pinned by `tests/bundle-size.test.mjs`), so a further population or
|
|
88
|
+
chart is one more of either rather than a menu per pairing.
|
|
89
|
+
|
|
90
|
+
- **Derived tests** — `WaferMapInput.derivedTests`: tests computed from other tests
|
|
91
|
+
on the same die, rather than measured. Each entry is a `TestDef` plus an
|
|
92
|
+
`expression`, and from the build onwards it is an ordinary test — it appears in
|
|
93
|
+
`result.testDefs`, in the value plot modes, the colorbar, tooltips,
|
|
94
|
+
`analyzeWaferMap`, Insights and the report, with no other change required.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
derivedTests: [
|
|
98
|
+
{ testNumber: 900001, name: 'Leakage Shift', unit: 'uA',
|
|
99
|
+
expression: 'abs(t[1020] - t[1010])', limitHigh: 5 },
|
|
100
|
+
{ testNumber: 900002, name: 'Sweep All Pass', testType: 'F',
|
|
101
|
+
expression: 'all(testPass[1010..1025])' },
|
|
102
|
+
]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Expressions read `t[n]` (measured value), `testPass[n]` (the tester's recorded
|
|
106
|
+
verdict), `specPass[n]` (spec-limit judgement) and `diePass()` (the die's bin
|
|
107
|
+
verdict) — the same `'test'`/`'spec'` distinction `passFailDisplay` already
|
|
108
|
+
draws, because they are different questions and a single accessor would have
|
|
109
|
+
silently meant "recorded verdict" for parametric data that has none. A range
|
|
110
|
+
(`t[1010..1015]`) yields a set that must be reduced by `mean`, `sum`, `min`,
|
|
111
|
+
`max`, `all`, `any`, `none`, `countTrue`, `countFalse` or `countKnown`; there is
|
|
112
|
+
no vector arithmetic, which is what stops the grammar becoming an array
|
|
113
|
+
language. Operators `+ - * / % ^`, comparisons, `and`/`or`/`not`, and
|
|
114
|
+
`if(cond, a, b)`.
|
|
115
|
+
|
|
116
|
+
Parsed to a typed tree at build time and walked per die — **no `eval`, no
|
|
117
|
+
`Function`, no third-party expression engine, and no way to name a host
|
|
118
|
+
object** — so a chart spec can be shared between teams as plain JSON. This is
|
|
119
|
+
why the root bundle grows ~5 KB gzip.
|
|
120
|
+
|
|
121
|
+
Evaluated on raw probe records **before** lot stacking and retest resolution, so
|
|
122
|
+
every derived value comes from one real touchdown: deriving after a stack would
|
|
123
|
+
subtract one aggregate from another, and deriving after retest collapse could mix
|
|
124
|
+
a value from one touchdown with a verdict from another.
|
|
125
|
+
|
|
126
|
+
A boolean expression is a verdict — declare `testType: 'F'` and the result lands
|
|
127
|
+
in `die.testPass`, never as a 1/0 in `testValues` where it would enter the
|
|
128
|
+
correlation matrix and the Cpk table. An unknown input yields an **absent** value
|
|
129
|
+
(no-data grey), never `0` and never `false`.
|
|
130
|
+
|
|
131
|
+
Everything statically knowable is checked at build and reported as a
|
|
132
|
+
`derived-test-invalid` warning naming the position — a parse or type error, a
|
|
133
|
+
`testNumber` colliding with measured data, a `testType` that disagrees with the
|
|
134
|
+
expression, `t[n]` on a functional test, `specPass[n]` on a test with no limits.
|
|
135
|
+
A rejected derived test is **dropped**, never half-applied.
|
|
136
|
+
|
|
137
|
+
- **Parametric sweeps** — `insights.sweeps` on `renderWaferMap`/`renderWaferGallery`:
|
|
138
|
+
an ordered run of tests read as a response curve rather than as independent
|
|
139
|
+
tests, with the crossing point of the first two series and the horizontal
|
|
140
|
+
separation at named levels measured and stated. `xValues` supplies the real
|
|
141
|
+
swept quantity so the crossing is reported in physical units; without it the
|
|
142
|
+
axis is the ordinal position, because test numbers are identifiers and are not
|
|
143
|
+
guaranteed to be evenly spaced. Each line is the population median with a
|
|
144
|
+
p10–p90 band. A multiple crossing says so; a level neither curve reaches reads
|
|
145
|
+
"not measurable" naming the series, never `0`.
|
|
146
|
+
- **Test ranges in a sweep's `tests`** — an entry may be `"1010..1030"`, the exact
|
|
147
|
+
syntax of a derived-test expression's `t[1010..1030]` and parsed by the same code
|
|
148
|
+
(`parseTestReference`), so the same text always names the same tests. A range
|
|
149
|
+
expands to the declared tests inside it, ascending — a program numbered in steps
|
|
150
|
+
of 2 needs no step syntax. `xValues` is checked against the expanded list: when
|
|
151
|
+
a test is missing from a range, the footer lists what the range matched and the
|
|
152
|
+
card is drawn in test order with the crossing and widths **not measured**
|
|
153
|
+
(`SweepData.notMeasured`), rather than sliding every later x value onto the
|
|
154
|
+
wrong test. The same now applies when only some series carry `xValues`, which
|
|
155
|
+
before put physical and ordinal x positions on one axis and reported an ordinal
|
|
156
|
+
crossing under the physical x label. A range resolves by filtering the declared
|
|
157
|
+
tests rather than counting through every integer, so `0..2000000000` in a shared
|
|
158
|
+
template is not a two-billion-step loop — in expressions too.
|
|
159
|
+
- **Nested derived tests** — a derived test may read another. Compilation
|
|
160
|
+
topologically sorts by what each expression reads, so declaration order does
|
|
161
|
+
not matter; cycles, self-references and dependents of a rejected test are
|
|
162
|
+
dropped with the cause named.
|
|
163
|
+
- New example: **Derived tests and sweeps** (`docs/examples/derived-tests.html`).
|
|
164
|
+
- **Sweeps have their own Insights sub-tab**, shown only when `insights.sweeps`
|
|
165
|
+
defines at least one; `InsightsView` gains `'sweeps'` for `defaultView`, which
|
|
166
|
+
falls back to `'overview'` when there are none. Not an option and not a count
|
|
167
|
+
threshold: a threshold would move a sweep between tabs the day a colleague
|
|
168
|
+
added another, and an option would be a layout flag with one right answer.
|
|
169
|
+
They never belonged in Distributions either — that view is driven by one
|
|
170
|
+
selected test (capability → boxplot → histogram → trend), while a sweep draws
|
|
171
|
+
many tests as one curve and ignores the selection.
|
|
172
|
+
- New example: **Parametric sweeps** (`docs/examples/sweeps.html`) — four
|
|
173
|
+
characterisation sweeps in the Sweeps tab, each a different use of the
|
|
174
|
+
crossing and the width: the temperature-inversion voltage (Fmax vs VDD at
|
|
175
|
+
25/125 °C, with the iso-frequency VDD gap at two speeds), DIBL from an NMOS
|
|
176
|
+
transfer curve at two drain biases, the retention bake at which the read
|
|
177
|
+
window closes (uneven read times, which is what `xValues` is for), and an
|
|
178
|
+
output driver's maximum drive current (VOH vs VOL under load). The transfer
|
|
179
|
+
curve is swept over **derived** `log10(t[n])` tests — a sweep reads any test,
|
|
180
|
+
which is how a log-scale curve is drawn without a log option.
|
|
181
|
+
- **Derived tests are marked on the sweep card**: `SweepPoint` carries `derived`
|
|
182
|
+
and `expression`, ordinal tick labels keep the `†` in front of a truncated name, the
|
|
183
|
+
tooltip marks each series row and adds each expression, and the footer shows
|
|
184
|
+
the key.
|
|
185
|
+
- **Sweep card text.** The crossing line lower-cased the y label ("Cell Vt"
|
|
186
|
+
became "cell vt", "Fmax" became "fmax") — the width line had already been
|
|
187
|
+
fixed for the same reason ("dBm" → "dbm"); widths printed in engineering
|
|
188
|
+
notation (`32.7E-3`); and an x label carrying its own unit doubled the
|
|
189
|
+
brackets (`(VDD (V))`). X values — ticks, crossing and widths — now share one
|
|
190
|
+
plain-decimal rule, and a width reads "0.0327 on the VDD (V) axis".
|
|
191
|
+
- The derived-tests example opened on a Switching Window measured at the step
|
|
192
|
+
where its two curves cross, so the gap sat around zero against `limitLow: 0`
|
|
193
|
+
and nearly every die carried a ▽. It is now measured at a fixed read step
|
|
194
|
+
with a 0.33 V margin, so only an edge band fails.
|
|
195
|
+
- **A derived test is now identifiable from `result.testDefs` alone.** `derived`
|
|
196
|
+
was declared only on the `DerivedTestDef` *input* type, while `result.testDefs`
|
|
197
|
+
is a homogeneous `TestDef[]` — so every display surface needed a cast to ask
|
|
198
|
+
whether a value was measured or derived, and `expression`/`constants` were
|
|
199
|
+
stripped off the admitted def entirely, leaving nothing to say where a number
|
|
200
|
+
came from. All three now travel on `TestDef` as `readonly` fields set by
|
|
201
|
+
`buildWaferMap`. This is the prerequisite for marking derived tests in the UI:
|
|
202
|
+
a display that cannot ask the question cannot mark it, and an engineer reading a
|
|
203
|
+
Cpk table should never be left to assume a number came off the tester. The
|
|
204
|
+
alternative — hosts holding their own `derivedTests` input beside the result and
|
|
205
|
+
joining by test number — is a second source of truth that can disagree with the
|
|
206
|
+
def actually admitted, which carries the resolved `testType`.
|
|
207
|
+
- **`testLabel` / `isDerivedTest` / `derivedTestSource` / `hasDerivedTests`**
|
|
208
|
+
(`renderer/testLabel.ts`, internal) — one definition of how a test is named and
|
|
209
|
+
how a derived one is distinguished. The "name, or *Test N* when it has none"
|
|
210
|
+
fallback existed in six places (`stats/sweep.ts`, `capability.ts`, `testPassRate.ts`,
|
|
211
|
+
`analyzeWaferMap.ts`, `charts/chartShell.ts`), all now routed through it. Around
|
|
212
|
+
sixteen surfaces display a test name; a marker added per surface would have been
|
|
213
|
+
that rule twenty-odd times over, which is how two of them end up disagreeing.
|
|
214
|
+
- **Derived tests are marked in the process capability panel.** A `†` precedes
|
|
215
|
+
the test name, the legend gains the key "† Derived, not measured", and the
|
|
216
|
+
tooltip says the same in words and shows the expression the value was
|
|
217
|
+
computed from. A dagger, not `ƒ`: in this domain `f` reads as femto,
|
|
218
|
+
and a `ƒ` beside `fA`/`fF` units is a real misread.
|
|
219
|
+
`TestCapability`/`CapabilityDatum` gain `derived` and `expression`.
|
|
220
|
+
- **…and everywhere else a test is named, always in front of the name.** In a
|
|
221
|
+
list the marks then form a column down the left edge, so a derived test is found
|
|
222
|
+
at a glance, and truncating a long name can never cut the mark off; a list
|
|
223
|
+
holding a derived test pads its measured names by the mark's width so names stay
|
|
224
|
+
aligned, and a list without one is unchanged. The map title and colorbar
|
|
225
|
+
(`† Leak Shift (nA)`)
|
|
226
|
+
with the key on its own line beneath, drawn via a new optional
|
|
227
|
+
`MapTitleParts.note`; the map tooltip, which adds the key and the expression;
|
|
228
|
+
finding labels and sentences, marked at the source so no reader of a finding
|
|
229
|
+
can show it unmarked, with `StatsFinding.variable.derived`/`expression` as the
|
|
230
|
+
structured form; `stats.functionalYield[].label` (plus `derived`/`expression`);
|
|
231
|
+
the Summary panel's parametric and functional tables and its findings list,
|
|
232
|
+
each with a key line naming every expression; and both HTML reports, whose key
|
|
233
|
+
lists the expressions because a printed report has no hover. CSV exports carry
|
|
234
|
+
plain names plus a trailing **Derived from** column, only when a row is
|
|
235
|
+
derived — a CSV has nowhere to put a key. The key is a single short string,
|
|
236
|
+
*Derived, not measured*, so it fits the corner of a map and cannot drift
|
|
237
|
+
between surfaces. Every surface goes through `renderer/testLabel.ts`, which
|
|
238
|
+
also replaced two more hand-written *Test N* fallbacks in `buildView.ts` and
|
|
239
|
+
one in the gallery's stacked cards, whose synthetic def now keeps the flag.
|
|
240
|
+
The finding tooltip now has one rule, `formatFindingTooltip`, which the
|
|
241
|
+
Summary panel shares with both reports instead of using `summary` directly.
|
|
242
|
+
The places a test is *chosen* are marked too: the plot-mode test menu (a slot
|
|
243
|
+
in front of each name, reserved only when a derived test is listed, and a key
|
|
244
|
+
line at the foot), the Insights test pickers (`testOptionLabels`), the
|
|
245
|
+
correlation matrix's axis labels and tooltip (key on its hint line; its CSV
|
|
246
|
+
gains per-side *derived from* columns) and the die list's column headers (key
|
|
247
|
+
line above the table; the CSV header states the expression). The last two
|
|
248
|
+
hand-written *Test N* fallbacks, in the plot-mode menu and the die list, now go
|
|
249
|
+
through `testLabel`.
|
|
250
|
+
- **`DERIVED_MARK` and `DERIVED_KEY` are exported** (root and `/renderer`), so a
|
|
251
|
+
host listing tests in its own UI marks derived tests exactly as the library does
|
|
252
|
+
instead of keeping its own copy of the glyph and the words. tsmap's test selector
|
|
253
|
+
is the first use.
|
|
254
|
+
- Getting there turned up two places that dropped the flag. **`mergeTestDefs`**,
|
|
255
|
+
which builds a gallery's single test list, rebuilt each def field by field and
|
|
256
|
+
never named `derived`, so every cross-wafer panel would have shown a derived
|
|
257
|
+
test unmarked while the single-wafer view marked it. It now carries `derived`
|
|
258
|
+
and `expression`, marking a test derived when **any** wafer says so: marking
|
|
259
|
+
a measured test is an oddity someone queries, while leaving a derived one
|
|
260
|
+
unmarked goes unnoticed. The Summary panel's per-test table narrowed defs the
|
|
261
|
+
same way and now keeps both fields. It also held a seventh copy of the
|
|
262
|
+
*Test N* fallback, now routed through `testLabel`.
|
|
263
|
+
|
|
264
|
+
### Changed
|
|
265
|
+
|
|
266
|
+
- **Docs and examples brought up to date with this release.** The developer
|
|
267
|
+
guide's sweep section no longer says `tests` has no range syntax or that a
|
|
268
|
+
sweep has no log option; it covers ranges, `xFromName`, `xUnit`, `xScale` and
|
|
269
|
+
drilldown. The sweeps example adds a fifth sweep (an RRAM resistance
|
|
270
|
+
distribution read from test names on a log axis) and gives the output-driver
|
|
271
|
+
sweep an x unit; the derived-tests example's sweep uses ranges and a real swept
|
|
272
|
+
quantity (pulse amplitude in volts) instead of ordinal steps, and places the †
|
|
273
|
+
in front of the name as everywhere else. The README, `AGENTS.md` and
|
|
274
|
+
`llms.txt` now mention derived tests, sweeps and drilldown.
|
|
275
|
+
Those two examples' code cards, a four-across grid that cut every code
|
|
276
|
+
block off at about half its width in 12 px type, now show one example per
|
|
277
|
+
row — the code whole, in monospace at 13.5 px, beside notes at 14.5 px,
|
|
278
|
+
stacked on narrow screens — from one shared style in `demo.css`. `--mono`,
|
|
279
|
+
which every example's code referenced and nothing defined, is now set.
|
|
280
|
+
The derived-tests example is now in two labelled parts — derived tests are
|
|
281
|
+
*data* (`buildWaferMap`'s `derivedTests`; tsmap's test-definitions file), a
|
|
282
|
+
sweep is a *chart* (the renderer's `insights.sweeps`; tsmap's separate sweeps
|
|
283
|
+
file) — because a run of look-alike snippets read as one list. Its snippets
|
|
284
|
+
show the whole call each belongs to, it says the sweep is in Insights →
|
|
285
|
+
Sweeps, with a *Show the sweep* button that presses the gallery's own Insights
|
|
286
|
+
button, and it links to the sweeps example. The sweeps example opens with
|
|
287
|
+
where its entries go.
|
|
288
|
+
|
|
289
|
+
- **`scripts/check-bundle-size.mjs` attributes lazy chunks by what imports
|
|
290
|
+
them, not by file name.** Once drilldown and Insights shared the chart panels,
|
|
291
|
+
esbuild split the shared code into an anonymous `chunk-*.js`, which a file-name
|
|
292
|
+
test counted as core. That would have added ~5 KB of lazy code to the "always
|
|
293
|
+
downloaded" figure. The guide, Insights and drilldown now count as their own
|
|
294
|
+
static closure minus what the entry already loads. The Insights figure now
|
|
295
|
+
includes the chart code it shares with drilldown, because opening Insights
|
|
296
|
+
downloads it.
|
|
297
|
+
- **Axis ticks sit on round values, and are labelled to their spacing.** Tick
|
|
298
|
+
labels used to take their decimals from each value's own size, never from the
|
|
299
|
+
gap between ticks, so a 1–10 axis read "2.00 4.00 6.00 8.00" and a bandgap's
|
|
300
|
+
1.195–1.205 V read "1.20" five times — a sloped curve labelled as a flat line.
|
|
301
|
+
And only the colorbar placed ticks on round numbers: the box plot, scatter,
|
|
302
|
+
trend and sweep divided the data range into equal fractions (2.54 / 2.04 /
|
|
303
|
+
1.53 GHz), the histogram labelled its bucket edges, and its count axis carried
|
|
304
|
+
an inline copy of the rounding rule with different breakpoints. Every numeric
|
|
305
|
+
axis now goes through `renderer/axisTicks.ts` — one 1-2-5 rounding rule
|
|
306
|
+
(`niceStep`), round ticks inside the data range, and labels with exactly the
|
|
307
|
+
decimals the step needs (`stepDecimals`): "2 4 6 8", "1.196 1.198 1.200".
|
|
308
|
+
**Density comes from the space, not a tick count:** `fitTicks` takes the
|
|
309
|
+
finest round step whose labels fit — measured label widths plus an 8 px gap
|
|
310
|
+
on a horizontal axis, a line spacing on a vertical one. A fixed count, tried
|
|
311
|
+
first, rounded a 200 px Idsat box plot (0.863–1.56 mA) to 1.0 / 1.2 / 1.4 —
|
|
312
|
+
fewer ticks than the five it replaced; it now reads 0.9 / 1.0 / … / 1.5. The colorbar's exact min and max are data values rather than grid
|
|
313
|
+
values, so they get one decimal more, dropped when it is a zero — a 9.87
|
|
314
|
+
maximum no longer rounds to "10". Log-scale and integer-valued colorbars, die
|
|
315
|
+
labels and limit labels keep size-based formatting; they are not tick grids.
|
|
316
|
+
Every chart's tick labels change, so doc screenshots need recapturing.
|
|
317
|
+
- **The wafer and lot reports' findings table now uses plain bin terms**
|
|
318
|
+
("hard bin 2", not "HBin 2"). Both HTML reports render one findings table,
|
|
319
|
+
`findingsTableHtml`; each report module had kept its own, and only the
|
|
320
|
+
findings-only report translated the bin terms.
|
|
321
|
+
|
|
322
|
+
### Deprecated
|
|
323
|
+
|
|
324
|
+
- **`renderFindingsReportHtml`** — removed in 0.31.0. Use
|
|
325
|
+
`renderWaferReportHtml(result, summary)` or `renderLotReportHtml(results)`:
|
|
326
|
+
their Findings section is the same table, alongside the population and yield
|
|
327
|
+
it was found in. It was kept in 0.30.1 on the grounds that the Summary panel
|
|
328
|
+
used it, which had not been true since 0.20.0; nothing in wmap or tsmap calls
|
|
329
|
+
it. It ships deprecated in this release, so it goes in 0.31.0 with the 0.30.0
|
|
330
|
+
deprecations. `deprecated()` now takes a removal version per name, for a name
|
|
331
|
+
deprecated too late to go with the rest.
|
|
332
|
+
|
|
333
|
+
### Fixed
|
|
334
|
+
|
|
335
|
+
- **A menu row's hint is no longer drawn under its own menu.** Menus sit in a
|
|
336
|
+
dedicated layer (`menuLayerFor`) that outranks everything else in its root,
|
|
337
|
+
and the shared tooltip was placed beside that layer — so the reason on a
|
|
338
|
+
greyed-out row (the drilldown menu's, the Overlays menu's) appeared behind the
|
|
339
|
+
menu it explained. `positionTooltip` now places the tooltip inside the
|
|
340
|
+
anchor's menu layer at `Z_ABOVE2`, above every menu there, still resolved per
|
|
341
|
+
root (a host `<dialog>`, a wmap modal).
|
|
342
|
+
|
|
343
|
+
- **The map's hover tooltip no longer jumps away from the die.** The shared
|
|
344
|
+
tooltip steps clear of its anchor when the anchor is under 40% of the window
|
|
345
|
+
tall — right for a toolbar button, which its tip must not cover, and wrong
|
|
346
|
+
for the map canvas, where the pointer is on the die the tip describes. Every
|
|
347
|
+
map that small (every gallery card, the smaller maps on a page such as the
|
|
348
|
+
geometry example, any map in a tall window) had its tip thrown below or above
|
|
349
|
+
the whole canvas, measured at up to 266 px from the pointer. The map now
|
|
350
|
+
passes `followPointer` to `positionTooltip` and the tip stays beside the
|
|
351
|
+
pointer; control tips keep stepping around their buttons.
|
|
352
|
+
|
|
353
|
+
- **An expanded chart grows into its modal, in the direction that suits it.**
|
|
354
|
+
Several cards kept their grid-card size inside the expand modal: the trend
|
|
355
|
+
and sweep plots stayed a few hundred pixels tall, ring and quadrant yield
|
|
356
|
+
kept a fixed-size circle, the correlation matrix kept small cells, process
|
|
357
|
+
capability stopped at ~160 px per test, and row charts (yield by wafer,
|
|
358
|
+
pareto, pass rate, value distribution) left most of a 700 px box empty. Each
|
|
359
|
+
card now declares how it grows (`setChartGrow`): **plots** fill width and
|
|
360
|
+
height in a wider box; **row charts** keep bars full-width, show every row the
|
|
361
|
+
box has room for, and the box opens sized to its rows (within the viewport);
|
|
362
|
+
**square** charts grow to the shorter side in a square box. Capability's
|
|
363
|
+
columns spread across the width while each box keeps its grid maximum width.
|
|
364
|
+
Growth past the grid size happens only while expanded, so the Insights grid
|
|
365
|
+
is unchanged; maximise/restore and drag-resize shrink back correctly.
|
|
366
|
+
- **Chart cards measure their chrome, not their stretch.**
|
|
367
|
+
`growCardToFitContent` took the card's overhead as `card height − body
|
|
368
|
+
height`, which counts any stretch as overhead and grows the card on every
|
|
369
|
+
redraw — why several panels opted out of stretching (`alignSelf: 'start'`),
|
|
370
|
+
which is what kept them small in the modal. It now sums the card's other
|
|
371
|
+
children, so a stretched card is harmless. Trend and sweep no longer ask for
|
|
372
|
+
the height they were given as their minimum, which would have stopped a
|
|
373
|
+
shrinking modal.
|
|
374
|
+
- **Chart tooltips wrap inside their background.** They were `nowrap` under a
|
|
375
|
+
280 px cap, so a longer line — a sweep row with its p10–p90 and n, a derived
|
|
376
|
+
test's expression — ran out past the dark box. They now wrap within 320 px,
|
|
377
|
+
breaking an unbroken expression if they must. The sweep tooltip puts each
|
|
378
|
+
series' spread and n on a line of their own under its median.
|
|
379
|
+
|
|
380
|
+
- **A functional-test finding merged across adjacent regions reported no
|
|
381
|
+
difference, and hid the real one.** Since 0.20.3, when a functional pass-rate
|
|
382
|
+
signal spanned adjacent rings, sectors or quadrants, the merge pass that joins
|
|
383
|
+
them into one finding (`Rings 1–2`) had no functional branch and fell through
|
|
384
|
+
to the bin one. It counted dies whose hard bin equalled `variable.bin` — which
|
|
385
|
+
a functional finding does not have — so both sides counted 0, and the merged
|
|
386
|
+
finding read *"Rings 1–2 has HBin undefined occurrence 0.0 percentage points
|
|
387
|
+
lower than the rest of the map"*, with a meaningless p-value. It also
|
|
388
|
+
**replaced** the correct per-region findings it merged, so a real functional
|
|
389
|
+
signal spanning adjacent regions was shown as no difference at all. The merge
|
|
390
|
+
now recomputes the pass rate over the merged region exactly as the per-region
|
|
391
|
+
builder does (verdicts through `getTestPassStatus`, dies without a verdict
|
|
392
|
+
excluded), and the sentence names the test. Found while testing the
|
|
393
|
+
derived-test marker, whose test wafer happened to have exactly this shape.
|
|
394
|
+
- **Merged-region findings used a singular verb:** *"Rings 1–2 has HBin 3
|
|
395
|
+
occurrence…"*, *"Quadrants NE & SE has…"*. A merged region is plural; bin and
|
|
396
|
+
functional pass-rate sentences now say "have". The Summary panel, which strips
|
|
397
|
+
the region from a row shown under its own group header, accepts either verb.
|
|
398
|
+
- **A `NaN` test value was classified as in-spec.** The spec-limit rule existed in
|
|
399
|
+
two places — `classifySpec` (die colouring, ▽/△ markers, the legend's spec tally)
|
|
400
|
+
and a private `specJudgement` in `stats/testPassRate.ts` (spec pass-rate tables) —
|
|
401
|
+
and they disagreed on two cases. `classifySpec` compared with `<` and `>`, which
|
|
402
|
+
are both false for `NaN`, so a non-finite measurement fell through to `'pass'`:
|
|
403
|
+
the exact "never drawn as plain in-spec" failure its own doc comment described.
|
|
404
|
+
It also reported `'pass'` for a test declaring no limits at all, inflating a spec
|
|
405
|
+
pass rate to 100% for every unlimited test. There is now one implementation
|
|
406
|
+
(`renderer/spec.ts`), strict on both, used by the renderer, stats and the
|
|
407
|
+
`specPass[n]` accessor.
|
|
408
|
+
|
|
409
|
+
## [0.30.2] — 2026-09-20
|
|
410
|
+
|
|
411
|
+
### Added
|
|
412
|
+
|
|
413
|
+
- **A gallery now reports its own progress, so a host's indicator can be honest about a long
|
|
414
|
+
load.** `GalleryOptions` gains two callbacks (API §6.2):
|
|
415
|
+
- `onItemResolved(resolved, total)` — the **advance** signal, fired as each card is built.
|
|
416
|
+
`total` is the count you passed, so it is right from the first call and a progress bar can be
|
|
417
|
+
sized before anything arrives.
|
|
418
|
+
- `onItemsResolved()` — the **settled** signal, fired once the gallery has finished: every
|
|
419
|
+
factory resolved *and* the lot-wide legend, shared colours and Summary panel up to date. On a
|
|
420
|
+
large lot the panel keeps filling in for seconds after the last card, so this is deliberately
|
|
421
|
+
later than "the cards are in" — a host clearing its indicator when the cards land leaves the
|
|
422
|
+
rest of the wait unexplained.
|
|
423
|
+
- Both fire whichever form the items took, so no host branches on the path the gallery chose: a
|
|
424
|
+
fully pre-built mount is one `onItemResolved` call with `resolved === total`, and
|
|
425
|
+
`onItemsResolved` always fires asynchronously, after `renderWaferGallery` has returned. Both
|
|
426
|
+
fire again on a rebuild (`setItems`, or switching into a stacked mode).
|
|
427
|
+
- **`CorrelationMatrix.sample`** — present only when the matrix was computed from a sample of the
|
|
428
|
+
dies, with `of` (the dies that carried test values) and `used` (how many were read).
|
|
429
|
+
The Insights correlation panel labels a sampled matrix from it; a host reading the matrix
|
|
430
|
+
directly must do the same.
|
|
431
|
+
|
|
432
|
+
### Changed
|
|
433
|
+
|
|
434
|
+
- **Correlation is computed from at most 25,000 dies.** It is the only analysis here that is
|
|
435
|
+
quadratic in tests and linear in dies — a 400,000-die, 50-test lot is 490 million pair updates,
|
|
436
|
+
which in a browser presents as a panel that never renders. Above that budget the dies are
|
|
437
|
+
sampled by an even stride across the whole population, never a prefix (dies arrive grouped by
|
|
438
|
+
wafer, so the first 25,000 of a lot are its first few wafers and would describe those rather
|
|
439
|
+
than the lot). At 25,000 dies the standard error of `r` is under 0.007, so the coefficients are
|
|
440
|
+
unchanged at the precision they are displayed to. **A sampled matrix always says so** — in the
|
|
441
|
+
Insights panel and in `CorrelationMatrix.sample`.
|
|
442
|
+
- **The lot and wafer Summary panels are built across tasks instead of in one block, and fill in
|
|
443
|
+
section by section.** Pooling every die of every wafer and deriving the bin, region and
|
|
444
|
+
per-test sections from it is the longest single piece of work this library does: 15.3 s on a
|
|
445
|
+
50-wafer lot of 4,000 dies × 100 tests, which is where a browser starts offering to kill the
|
|
446
|
+
page. It is now around 335 steps of a few hundred ms at most. An ordinary lot still renders in
|
|
447
|
+
one synchronous pass and looks exactly as it did. A panel render in flight is abandoned when
|
|
448
|
+
the lot, filter or highlight changes, and on `destroy()`.
|
|
449
|
+
- **The same panel work is also about 2.3× cheaper, and a lot pays for it once.** The per-test
|
|
450
|
+
pass over a lot's pooled dies is memoised on the population it describes, so the lot Summary
|
|
451
|
+
panel, the Insights Overview and the Insights capability chart share one computation instead of
|
|
452
|
+
running three; the sort inside it uses a `Float64Array` (4× on the whole pooled pass); and the
|
|
453
|
+
Test Values rows are built in one pass instead of three.
|
|
454
|
+
- **Switching Insights tabs no longer rebuilds the panel you are returning to.** A built section
|
|
455
|
+
is kept per view, so going back to Distributions is instant rather than re-running capability,
|
|
456
|
+
boxplot, histogram and trend (about 5 s at 400,000 dies). Only a pure tab switch reuses it —
|
|
457
|
+
new data, Group by, scope or an axis preference still rebuilds everything.
|
|
458
|
+
- **A lot-wide option is pushed to the cards only when its value actually changed.** `binColors`,
|
|
459
|
+
`valueRange` and `metadataValueOrder` are re-derived from the whole population as each wafer
|
|
460
|
+
resolves, and each derivation allocates a fresh object, so every card was being rebuilt and
|
|
461
|
+
redrawn for an identical value. On a progressive load that is quadratic: on 50 wafers × 8,000
|
|
462
|
+
dies × 50 tests in Chrome the bin-colour map alone was 1,275 pushes and 24 s of a 64 s load,
|
|
463
|
+
and is now one push.
|
|
464
|
+
- **The gallery's lot Summary panel now settles once, when the last wafer resolves, instead of
|
|
465
|
+
re-rendering after every one.** A progressive load was re-pooling and redrawing the whole
|
|
466
|
+
panel on each newly resolved wafer, which is quadratic in wafer count and was the single
|
|
467
|
+
largest cost on this path: 175 s of a 182 s, 50-card progressive load. The panel still renders
|
|
468
|
+
once at mount and again whenever a user opens it. Combined with the change above, 50 cards of
|
|
469
|
+
8,000 dies now load in **7.7 s of non-blocking work with no task over 440 ms**, down from
|
|
470
|
+
182 s.
|
|
471
|
+
|
|
472
|
+
### Fixed
|
|
473
|
+
|
|
474
|
+
- **A large wafer or lot could fail outright with "Maximum call stack size exceeded".**
|
|
475
|
+
`Math.min(...)`/`Math.max(...)` spread a whole die array into a call, which throws once the
|
|
476
|
+
array passes roughly 100,000 entries — reachable on a single 400,000-die wafer in aggregated
|
|
477
|
+
values, inferred wafer geometry, reticle bounds and several chart scales. Every such call now
|
|
478
|
+
uses the `minOf`/`maxOf` helpers, and `scripts/check-spread-limits.mjs` (wired into
|
|
479
|
+
`npm run check`) fails the build if one comes back.
|
|
480
|
+
- **A non-finite test value could make a whole test's statistics `NaN`.** `computePerTestStats`
|
|
481
|
+
sorted and summarised whatever `testValues` held, with no `Number.isFinite` screen, so one
|
|
482
|
+
`NaN` or `Infinity` from a parser silently turned that test's mean, median, sigma and Cpk into
|
|
483
|
+
`NaN` rather than being dropped as no data.
|
|
484
|
+
|
|
25
485
|
## [0.30.1] — 2026-09-16
|
|
26
486
|
|
|
27
487
|
### Security
|
|
28
488
|
|
|
29
|
-
- **
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
`<img src=x onerror=…>` ran that script when a user hovered, in a Tauri, Electron or
|
|
34
|
-
WebView2 host inside the app's own webview. Every such value is now escaped. The escape
|
|
35
|
-
helper, formerly private to the reports, is the one copy for the library (`core/utils.ts`),
|
|
36
|
-
and `tests/htmlEscaping.test.mjs` fails if a tooltip puts a raw label into HTML again.
|
|
489
|
+
- **Fixes a security issue in how names from a data file are displayed.** Earlier versions are
|
|
490
|
+
affected; update to 0.30.1, particularly if your app opens files from sources you don't
|
|
491
|
+
control. Names now always display as plain text, so a name that contains markup shows it
|
|
492
|
+
literally.
|
|
37
493
|
|
|
38
494
|
### Changed
|
|
39
495
|
|