@zio.dev/zio-blocks 0.0.33 → 0.0.51

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.
Files changed (150) hide show
  1. package/guides/compile-time-resource-safety-with-scope.md +16 -17
  2. package/guides/getting-started-with-mux.md +1507 -0
  3. package/guides/query-dsl-extending.md +161 -102
  4. package/guides/query-dsl-fluent-builder.md +217 -157
  5. package/guides/query-dsl-reified-optics.md +12 -10
  6. package/guides/query-dsl-sql.md +246 -165
  7. package/guides/telemetry-guide.md +1069 -0
  8. package/guides/zio-schema-migration.md +29 -22
  9. package/index.md +292 -50
  10. package/package.json +1 -1
  11. package/plans/config-follow-up-prs.md +188 -0
  12. package/plans/config-pr-assessment-roadmap.md +310 -0
  13. package/reference/MuxDataFlow.jsx +250 -0
  14. package/reference/async.md +651 -0
  15. package/reference/chunk.md +3533 -308
  16. package/reference/codegen/case-class.md +436 -0
  17. package/reference/codegen/emitter-config.md +383 -0
  18. package/reference/codegen/examples.md +664 -0
  19. package/reference/codegen/field.md +316 -0
  20. package/reference/codegen/index.md +317 -0
  21. package/reference/codegen/scala-emitter.md +392 -0
  22. package/reference/codegen/scala-file.md +276 -0
  23. package/reference/codegen/sealed-trait.md +408 -0
  24. package/reference/codegen/type-definition.md +340 -0
  25. package/reference/codegen/type-ref.md +201 -0
  26. package/reference/combinators.md +347 -117
  27. package/reference/config.md +158 -0
  28. package/reference/context.md +4 -4
  29. package/reference/datastar.md +346 -0
  30. package/reference/docs.md +1461 -345
  31. package/reference/endpoint/auth-type.md +146 -0
  32. package/reference/endpoint/endpoint.md +297 -0
  33. package/reference/endpoint/http-codec.md +249 -0
  34. package/reference/endpoint/index.md +825 -0
  35. package/reference/endpoint/path-codec.md +237 -0
  36. package/reference/endpoint/route-pattern.md +196 -0
  37. package/reference/endpoint/route-tree.md +111 -0
  38. package/reference/endpoint/segment-codec.md +212 -0
  39. package/reference/html.md +1120 -0
  40. package/reference/htmx/attribute-values.md +359 -0
  41. package/reference/htmx/hx-encoding.md +111 -0
  42. package/reference/htmx/hx-params.md +204 -0
  43. package/reference/htmx/hx-swap.md +276 -0
  44. package/reference/htmx/hx-sync.md +251 -0
  45. package/reference/htmx/hx-target.md +314 -0
  46. package/reference/htmx/hx-trigger.md +457 -0
  47. package/reference/htmx/hx-url-update.md +239 -0
  48. package/reference/htmx/index.md +855 -0
  49. package/reference/http-model/index.md +47 -0
  50. package/reference/http-model/model.md +1481 -0
  51. package/reference/http-model/schema.md +747 -0
  52. package/reference/maybe.md +826 -0
  53. package/reference/media-type.md +2 -2
  54. package/reference/mux.mdx +823 -0
  55. package/reference/openapi.md +1351 -0
  56. package/reference/resource-management/defer-handle.md +1 -1
  57. package/reference/resource-management/resource.md +31 -2
  58. package/reference/resource-management/scope.md +28 -12
  59. package/reference/resource-management/wire.md +3 -7
  60. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  61. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  62. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  63. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  64. package/reference/ringbuffer/advanced.mdx +109 -0
  65. package/reference/ringbuffer/index.mdx +145 -0
  66. package/reference/ringbuffer/mpmc.mdx +151 -0
  67. package/reference/ringbuffer/mpsc.mdx +132 -0
  68. package/reference/ringbuffer/spmc.mdx +108 -0
  69. package/reference/ringbuffer/spsc.mdx +344 -0
  70. package/reference/{allows.md → schema/allows.md} +4 -4
  71. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  72. package/reference/{binding.md → schema/binding.md} +2 -3
  73. package/reference/schema/built-in-codecs/avro.md +451 -0
  74. package/reference/schema/built-in-codecs/bson.md +480 -0
  75. package/reference/schema/built-in-codecs/csv.md +564 -0
  76. package/reference/schema/built-in-codecs/index.md +77 -0
  77. package/reference/schema/built-in-codecs/json/index.md +295 -0
  78. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  79. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
  80. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  81. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  82. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  83. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  84. package/reference/schema/built-in-codecs/thrift.md +433 -0
  85. package/reference/schema/built-in-codecs/toon.md +1078 -0
  86. package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
  87. package/reference/schema/built-in-codecs/yaml.md +552 -0
  88. package/reference/{codec.md → schema/codec.md} +10 -10
  89. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
  90. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  91. package/reference/schema/format.md +92 -0
  92. package/reference/schema/index.md +50 -0
  93. package/reference/schema/migration.md +297 -0
  94. package/reference/{modifier.md → schema/modifier.md} +58 -7
  95. package/reference/{optics.md → schema/optics.md} +2 -2
  96. package/reference/{patch.md → schema/patch.md} +1 -1
  97. package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
  98. package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
  99. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  100. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  101. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  102. package/reference/{schema.md → schema/schema.md} +12 -0
  103. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  104. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  105. package/reference/smithy.md +533 -0
  106. package/reference/sql/db-codec-deriver.md +71 -0
  107. package/reference/sql/db-codec.md +687 -0
  108. package/reference/sql/db-con.md +271 -0
  109. package/reference/sql/db-connection.md +153 -0
  110. package/reference/sql/db-param-writer.md +77 -0
  111. package/reference/sql/db-param.md +66 -0
  112. package/reference/sql/db-result-reader.md +146 -0
  113. package/reference/sql/db-tx.md +82 -0
  114. package/reference/sql/db-value.md +41 -0
  115. package/reference/sql/ddl.md +85 -0
  116. package/reference/sql/frag.md +254 -0
  117. package/reference/sql/index.md +341 -0
  118. package/reference/sql/repo.md +600 -0
  119. package/reference/sql/sql-dialect.md +73 -0
  120. package/reference/sql/sql-logger.md +62 -0
  121. package/reference/sql/sql-name-mapper.md +70 -0
  122. package/reference/sql/table-metadata.md +134 -0
  123. package/reference/sql/table.md +448 -0
  124. package/reference/sql/transactor-zio.md +399 -0
  125. package/reference/sql/transactor.md +353 -0
  126. package/reference/sql-zio.md +112 -0
  127. package/reference/streams/concurrent-operators.md +106 -0
  128. package/reference/streams/index.md +653 -0
  129. package/reference/streams/pipeline.md +718 -0
  130. package/reference/streams/reader.md +1284 -0
  131. package/reference/streams/scala-2-compatibility.md +55 -0
  132. package/reference/streams/sink.md +1426 -0
  133. package/reference/streams/stream.md +2526 -0
  134. package/reference/streams/writer.md +1045 -0
  135. package/reference/streams/zero-boxing.md +275 -0
  136. package/reference/telemetry.md +693 -0
  137. package/reference/typeid.md +5 -19
  138. package/sidebars.js +238 -43
  139. package/reference/formats.md +0 -694
  140. package/reference/http-model.md +0 -1716
  141. package/reference/streams.md +0 -989
  142. package/ringbuffer.md +0 -249
  143. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  144. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  145. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  146. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  147. /package/reference/{registers.md → schema/registers.md} +0 -0
  148. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  149. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  150. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -0,0 +1,680 @@
1
+ import React, { useState, useCallback, useRef } from "react";
2
+
3
+ const CAP = 4;
4
+ const MASK = CAP - 1;
5
+
6
+ const COLOR = {
7
+ write: "#1D9E75",
8
+ read: "#378ADD",
9
+ fail: "#E24B4A",
10
+ neutral: "#888780",
11
+ stale: "#C0BDB4",
12
+ };
13
+
14
+ function initState() {
15
+ return {
16
+ buf: Array(CAP).fill(null), // actual slot contents — never cleared by consumers
17
+ live: Array(CAP).fill(false), // is slot logically occupied (cIdx ≤ slot < pIdx)?
18
+ pIdx: 0,
19
+ cIdx: 0,
20
+ };
21
+ }
22
+
23
+ // ── Prose step summary ────────────────────────────────────────────────────────
24
+
25
+ function stepDescription(calc) {
26
+ if (!calc) return null;
27
+
28
+ if (calc.op === "offer") {
29
+ if (calc.size >= CAP) {
30
+ return {
31
+ color: COLOR.fail,
32
+ paragraphs: [
33
+ `The producer reads pIdx = ${calc.pIdx} with a plain load — no CAS is needed ` +
34
+ `since there is exactly one producer. It reads cIdx = ${calc.cIdx} to check capacity. ` +
35
+ `The occupancy pIdx − cIdx = ${calc.size} equals the buffer capacity (${CAP}), ` +
36
+ `so every slot holds an unconsumed element.`,
37
+
38
+ `offer() returns false immediately. No slot is written and pIdx stays at ${calc.pIdx}.`,
39
+ ],
40
+ };
41
+ } else {
42
+ return {
43
+ color: COLOR.write,
44
+ paragraphs: [
45
+ `The producer reads pIdx = ${calc.pIdx} with a plain load — no CAS is needed ` +
46
+ `since there is exactly one producer. The occupancy pIdx − cIdx = ${calc.size} ` +
47
+ `is less than the capacity (${CAP}), so there is at least one free slot. ` +
48
+ `The target slot is computed as pIdx & mask = ${calc.pIdx} & ${MASK} = ${calc.slot}.`,
49
+
50
+ `The producer first writes the element into buf[${calc.slot}] with a release store, ` +
51
+ `then advances pIdx from ${calc.pIdx} to ${calc.pIdx + 1} with a second release store. ` +
52
+ `The ordering matters: pIdx is advanced last. A consumer that reads pIdx = ${calc.pIdx + 1} ` +
53
+ `with an acquire load is guaranteed to also see the element already sitting in buf[${calc.slot}].`,
54
+ ],
55
+ };
56
+ }
57
+ } else {
58
+ if (calc.size === 0) {
59
+ return {
60
+ color: COLOR.fail,
61
+ paragraphs: [
62
+ `A consumer reads cIdx = ${calc.cIdx} (volatile) and pIdx = ${calc.pIdx} (acquire). ` +
63
+ `Since pIdx equals cIdx, the buffer contains no elements. ` +
64
+ `take() returns null immediately — no CAS is attempted and cIdx stays at ${calc.cIdx}.`,
65
+ ],
66
+ };
67
+ } else {
68
+ return {
69
+ color: COLOR.read,
70
+ paragraphs: [
71
+ `A consumer reads cIdx = ${calc.cIdx} (volatile) and pIdx = ${calc.pIdx} (acquire). ` +
72
+ `Since pIdx (${calc.pIdx}) > cIdx (${calc.cIdx}), the buffer is not empty. ` +
73
+ `The slot is computed as cIdx & mask = ${calc.cIdx} & ${MASK} = ${calc.slot}.`,
74
+
75
+ `The consumer reads buf[${calc.slot}] = "${calc.element}" BEFORE attempting the CAS. ` +
76
+ `This ordering is critical: once the CAS advances cIdx to ${calc.cIdx + 1}, ` +
77
+ `the producer is permitted to overwrite slot ${calc.slot} on its next lap. ` +
78
+ `Reading first ensures the consumer captures the value while it is still exclusively theirs.`,
79
+
80
+ `The CAS on cIdx from ${calc.cIdx} to ${calc.cIdx + 1} succeeds. ` +
81
+ `Unlike MPSC, buf[${calc.slot}] is NOT cleared — the slot retains "${calc.element}" ` +
82
+ `until the producer wraps around and overwrites it. ` +
83
+ `Element validity is determined by comparing pIdx and cIdx, never by null-checking slots.`,
84
+ ],
85
+ };
86
+ }
87
+ }
88
+ }
89
+
90
+ function StepDescription({ calc }) {
91
+ const desc = stepDescription(calc);
92
+
93
+ if (!desc) return (
94
+ <div style={{
95
+ margin: "6px 0 4px", borderRadius: 8,
96
+ border: "1px solid #e8e6df", background: "#f5f4f0",
97
+ padding: "10px 14px", fontSize: 12,
98
+ color: "#ccc", textAlign: "center", fontStyle: "italic",
99
+ }}>
100
+ step summary will appear here after each operation
101
+ </div>
102
+ );
103
+
104
+ return (
105
+ <div style={{
106
+ margin: "6px 0 4px", borderRadius: 8,
107
+ border: `1px solid ${desc.color}22`,
108
+ background: `${desc.color}08`,
109
+ padding: "10px 14px",
110
+ }}>
111
+ {desc.paragraphs.map((p, i) => (
112
+ <p key={i} style={{
113
+ margin: i < desc.paragraphs.length - 1 ? "0 0 6px" : "0",
114
+ fontSize: 12.5, lineHeight: 1.65, color: "#444",
115
+ }}>
116
+ {p}
117
+ </p>
118
+ ))}
119
+ </div>
120
+ );
121
+ }
122
+
123
+ // ── Algorithm trace panel ─────────────────────────────────────────────────────
124
+
125
+ function CalcPanel({ calc }) {
126
+ if (!calc) return (
127
+ <div style={{
128
+ margin: "6px 0 0", borderRadius: 8,
129
+ border: "1px solid #e8e6df", background: "#f5f4f0",
130
+ padding: "10px 14px", fontSize: 11,
131
+ fontFamily: "monospace", color: "#ccc", textAlign: "center",
132
+ }}>
133
+ algorithm trace will appear here after each operation
134
+ </div>
135
+ );
136
+
137
+ const isOffer = calc.op === "offer";
138
+ const opColor = isOffer ? COLOR.write : COLOR.read;
139
+
140
+ let rows;
141
+ let decisionColor;
142
+ let decisionText;
143
+
144
+ if (isOffer) {
145
+ const full = calc.size >= CAP;
146
+ rows = [
147
+ {
148
+ label: "pIdx",
149
+ expr: "pIdx",
150
+ val: String(calc.pIdx),
151
+ note: "plain read — single producer, no CAS needed",
152
+ hi: false,
153
+ },
154
+ {
155
+ label: "cIdx",
156
+ expr: "cIdx",
157
+ val: String(calc.cIdx),
158
+ note: "read to check capacity",
159
+ hi: false,
160
+ },
161
+ {
162
+ label: "size",
163
+ expr: "pIdx − cIdx",
164
+ val: String(calc.size),
165
+ note: `${calc.pIdx} − ${calc.cIdx} = ${calc.size} (current occupancy)`,
166
+ hi: true,
167
+ },
168
+ ...(!full ? [{
169
+ label: "slot",
170
+ expr: "pIdx & mask",
171
+ val: String(calc.slot),
172
+ note: `${calc.pIdx} & ${MASK} = ${calc.slot} (bitmask replaces mod)`,
173
+ hi: false,
174
+ }] : []),
175
+ ];
176
+ decisionColor = full ? COLOR.fail : COLOR.write;
177
+ decisionText = full
178
+ ? `✗ size == cap → buffer FULL → return false`
179
+ : `✓ size < cap → write buf[${calc.slot}] (release), then pIdx ${calc.pIdx}→${calc.pIdx + 1} (release) → return true`;
180
+ } else {
181
+ const empty = calc.size === 0;
182
+ rows = [
183
+ {
184
+ label: "cIdx",
185
+ expr: "cIdx",
186
+ val: String(calc.cIdx),
187
+ note: "volatile read — any consumer thread may hold this",
188
+ hi: false,
189
+ },
190
+ {
191
+ label: "pIdx",
192
+ expr: "pIdx",
193
+ val: String(calc.pIdx),
194
+ note: "acquire read — sees all writes up to this pIdx",
195
+ hi: false,
196
+ },
197
+ {
198
+ label: "size",
199
+ expr: "pIdx − cIdx",
200
+ val: String(calc.size),
201
+ note: `${calc.pIdx} − ${calc.cIdx} = ${calc.size} (${empty ? "== 0 → empty" : "> 0 → not empty"})`,
202
+ hi: true,
203
+ },
204
+ ...(!empty ? [
205
+ {
206
+ label: "slot",
207
+ expr: "cIdx & mask",
208
+ val: String(calc.slot),
209
+ note: `${calc.cIdx} & ${MASK} = ${calc.slot} (bitmask replaces mod)`,
210
+ hi: false,
211
+ },
212
+ {
213
+ label: "element",
214
+ expr: "buf[slot]",
215
+ val: `"${calc.element}"`,
216
+ note: `read BEFORE CAS — once cIdx advances, producer may overwrite slot ${calc.slot}`,
217
+ hi: true,
218
+ },
219
+ ] : []),
220
+ ];
221
+ decisionColor = empty ? COLOR.fail : COLOR.read;
222
+ decisionText = empty
223
+ ? `✗ pIdx == cIdx → buffer EMPTY → return null`
224
+ : `✓ element captured → CAS cIdx ${calc.cIdx}→${calc.cIdx + 1} → return "${calc.element}" (buf[${calc.slot}] not cleared)`;
225
+ }
226
+
227
+ return (
228
+ <div style={{
229
+ margin: "6px 0 0", borderRadius: 8,
230
+ border: `1px solid ${opColor}30`,
231
+ overflow: "hidden", fontSize: 12,
232
+ }}>
233
+ <div style={{
234
+ background: `${opColor}14`, padding: "5px 12px",
235
+ display: "flex", alignItems: "center", gap: 8,
236
+ borderBottom: `1px solid ${opColor}20`,
237
+ }}>
238
+ <span style={{ color: opColor, fontFamily: "monospace", fontWeight: 700, fontSize: 12 }}>
239
+ {isOffer ? "offer()" : "take()"}
240
+ </span>
241
+ <span style={{ color: "#bbb", fontSize: 11 }}>— algorithm trace</span>
242
+ </div>
243
+
244
+ {rows.map((r, i) => (
245
+ <div key={i} style={{
246
+ display: "grid",
247
+ gridTemplateColumns: "58px 140px 52px 1fr",
248
+ alignItems: "center",
249
+ padding: "4px 12px",
250
+ borderBottom: i < rows.length - 1 ? "1px solid #f0ede6" : "none",
251
+ background: r.hi ? `${opColor}0c` : "transparent",
252
+ }}>
253
+ <span style={{
254
+ fontFamily: "monospace", fontSize: 11,
255
+ fontWeight: r.hi ? 700 : 400,
256
+ color: r.hi ? opColor : "#aaa",
257
+ }}>
258
+ {r.label}
259
+ </span>
260
+ <span style={{ fontFamily: "monospace", fontSize: 11, color: "#ccc" }}>
261
+ = {r.expr}
262
+ </span>
263
+ <span style={{
264
+ fontFamily: "monospace", fontSize: 14, fontWeight: 700,
265
+ color: r.hi ? decisionColor : "#555",
266
+ textAlign: "right", paddingRight: 10,
267
+ }}>
268
+ {r.val}
269
+ </span>
270
+ <span style={{
271
+ fontFamily: "monospace", fontSize: 10,
272
+ color: r.hi ? decisionColor + "aa" : "#d8d5cc",
273
+ }}>
274
+ {r.note}
275
+ </span>
276
+ </div>
277
+ ))}
278
+
279
+ <div style={{
280
+ padding: "6px 12px",
281
+ background: `${decisionColor}10`,
282
+ borderTop: `1px solid ${decisionColor}28`,
283
+ fontFamily: "monospace", fontSize: 11,
284
+ fontWeight: 600, color: decisionColor,
285
+ }}>
286
+ {decisionText}
287
+ </div>
288
+ </div>
289
+ );
290
+ }
291
+
292
+ // ── SVG ring diagram ──────────────────────────────────────────────────────────
293
+
294
+ function RingDiagram({ buf, live, pIdx, cIdx, hiSlots, hiColor }) {
295
+ const size = pIdx - cIdx;
296
+ const pSlot = pIdx & MASK;
297
+ const cSlot = cIdx & MASK;
298
+ const same = pSlot === cSlot;
299
+
300
+ const SX = [56, 186, 316, 446];
301
+ const SW = 106, SH = 78, SY = 30;
302
+ const bot = SY + SH;
303
+
304
+ const pCX = SX[pSlot] + SW / 2;
305
+ const cCX = SX[cSlot] + SW / 2;
306
+ const pX = same ? pCX - 28 : pCX;
307
+ const cX = same ? cCX + 28 : cCX;
308
+
309
+ const szColor = size === CAP ? COLOR.fail : COLOR.neutral;
310
+
311
+ const statItems = [
312
+ { label: "size", val: `${size} / ${CAP}`, col: szColor },
313
+ { label: "pIdx", val: `${pIdx}`, col: COLOR.write },
314
+ { label: "cIdx", val: `${cIdx}`, col: COLOR.read },
315
+ ];
316
+
317
+ return (
318
+ <svg viewBox="0 0 608 210" style={{ width: "100%", display: "block" }}>
319
+ <defs>
320
+ <marker id="arr-spmc" viewBox="0 0 10 10" refX="8" refY="5"
321
+ markerWidth="5" markerHeight="5" orient="auto-start-reverse">
322
+ <path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke"
323
+ strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
324
+ </marker>
325
+ </defs>
326
+
327
+ {SX.map((x, i) => {
328
+ const hi = hiSlots?.includes(i);
329
+ const val = buf[i];
330
+ const isLive = live[i];
331
+ const isStale = !isLive && val !== null;
332
+ const col = hi ? hiColor : undefined;
333
+
334
+ const valText = val ?? "—";
335
+ const valColor = hi
336
+ ? hiColor
337
+ : isLive
338
+ ? COLOR.write
339
+ : isStale
340
+ ? COLOR.stale
341
+ : "#ccc";
342
+
343
+ const borderColor = hi ? col : isLive ? "#aaa" : "#d4d2c8";
344
+ const borderWidth = hi ? 1.5 : 0.5;
345
+ const fillColor = hi
346
+ ? col + "18"
347
+ : isStale
348
+ ? "#f8f7f4"
349
+ : "var(--slot-bg, #f5f5f3)";
350
+
351
+ return (
352
+ <g key={i}>
353
+ <rect x={x} y={SY} width={SW} height={SH} rx="8"
354
+ fill={fillColor}
355
+ stroke={borderColor}
356
+ strokeWidth={borderWidth}
357
+ strokeDasharray={isStale && !hi ? "4 3" : undefined} />
358
+
359
+ {/* Slot label */}
360
+ <text x={x + SW / 2} y={SY + 11} textAnchor="middle"
361
+ dominantBaseline="central"
362
+ fontSize="10" fill="#888" fontFamily="monospace">
363
+ slot {i}
364
+ </text>
365
+
366
+ {/* Value */}
367
+ <text x={x + SW / 2} y={SY + SH / 2 + (isStale ? 0 : 4)} textAnchor="middle"
368
+ dominantBaseline="central"
369
+ fontSize={val ? 20 : 13} fontWeight={isLive ? "600" : "400"}
370
+ fontFamily={val ? "monospace" : "sans-serif"}
371
+ fill={valColor}>
372
+ {valText}
373
+ </text>
374
+
375
+ {/* "stale" badge for consumed-but-not-overwritten slots */}
376
+ {isStale && !hi && (
377
+ <text x={x + SW / 2} y={SY + SH - 10} textAnchor="middle"
378
+ dominantBaseline="central"
379
+ fontSize="9" fill={COLOR.stale} fontFamily="monospace">
380
+ stale
381
+ </text>
382
+ )}
383
+ </g>
384
+ );
385
+ })}
386
+
387
+ {/* pIdx arrow */}
388
+ <line x1={pX} y1={bot + 2} x2={pX} y2={bot + 12}
389
+ stroke={COLOR.write} strokeWidth="1.5" markerEnd="url(#arr-spmc)" />
390
+ <text x={pX} y={bot + 24} textAnchor="middle" dominantBaseline="central"
391
+ fontSize="11" fontFamily="monospace" fill={COLOR.write}>
392
+ pIdx={pIdx}
393
+ </text>
394
+
395
+ {/* cIdx arrow */}
396
+ <line x1={cX} y1={bot + 2} x2={cX} y2={bot + 12}
397
+ stroke={COLOR.read} strokeWidth="1.5" markerEnd="url(#arr-spmc)" />
398
+ <text x={cX} y={bot + 24} textAnchor="middle" dominantBaseline="central"
399
+ fontSize="11" fontFamily="monospace" fill={COLOR.read}>
400
+ cIdx={cIdx}
401
+ </text>
402
+
403
+ {/* Stats */}
404
+ {statItems.map((s, n) => {
405
+ const sx = 100 + n * 200;
406
+ return (
407
+ <g key={n}>
408
+ <text x={sx} y={166} textAnchor="middle" dominantBaseline="central"
409
+ fontSize="9" fill="#aaa" fontFamily="sans-serif">
410
+ {s.label}
411
+ </text>
412
+ <text x={sx} y={182} textAnchor="middle" dominantBaseline="central"
413
+ fontSize="14" fontWeight="500" fontFamily="monospace" fill={s.col}>
414
+ {s.val}
415
+ </text>
416
+ </g>
417
+ );
418
+ })}
419
+
420
+ {/* Legend */}
421
+ {[
422
+ [COLOR.write, "pIdx — single producer"],
423
+ [COLOR.read, "cIdx — consumers (CAS)"],
424
+ [COLOR.stale, "stale — consumed, not yet overwritten"],
425
+ ].map(([c, label], n) => (
426
+ <g key={n}>
427
+ <circle cx={34 + n * 192} cy={203} r="4" fill={c} />
428
+ <text x={44 + n * 192} y={203} dominantBaseline="central"
429
+ fontSize="10" fill="#999" fontFamily="sans-serif">
430
+ {label}
431
+ </text>
432
+ </g>
433
+ ))}
434
+ </svg>
435
+ );
436
+ }
437
+
438
+ // ── History log ───────────────────────────────────────────────────────────────
439
+
440
+ function HistoryLog({ entries, currentIndex }) {
441
+ const bottomRef = useRef(null);
442
+
443
+ return (
444
+ <div style={{
445
+ maxHeight: 110, overflowY: "auto",
446
+ border: "1px solid #e0ded6", borderRadius: 8,
447
+ fontSize: 11, fontFamily: "monospace",
448
+ }}>
449
+ {entries.map((e, i) => {
450
+ const border = { write: COLOR.write, read: COLOR.read, fail: COLOR.fail }[e.kind] ?? "#ccc";
451
+ const isSelected = currentIndex === i;
452
+ return (
453
+ <div key={i} style={{
454
+ display: "flex", gap: 10, padding: "4px 10px",
455
+ borderLeft: `3px solid ${border}`,
456
+ borderBottom: i < entries.length - 1 ? "1px solid #f0ede6" : "none",
457
+ color: border,
458
+ background: isSelected ? `${border}0a` : "transparent",
459
+ fontWeight: isSelected ? 600 : 400,
460
+ }}>
461
+ <span style={{ color: "#bbb", minWidth: 32, fontSize: 10 }}>#{i + 1}</span>
462
+ <span style={{ flex: 1 }}>{e.msg}</span>
463
+ </div>
464
+ );
465
+ })}
466
+ <div ref={bottomRef} />
467
+ </div>
468
+ );
469
+ }
470
+
471
+ // ── Main component ────────────────────────────────────────────────────────────
472
+
473
+ export default function SpmcRingBuffer() {
474
+ const [state, setState] = useState(initState);
475
+ const [input, setInput] = useState("A");
476
+ const [history, setHistory] = useState([{
477
+ msg: "initial state", kind: "neutral",
478
+ stateSnapshot: initState(), calcSnapshot: null,
479
+ }]);
480
+ const [hi, setHi] = useState({ slots: null, color: null });
481
+ const [calc, setCalc] = useState(null);
482
+ const [historyIndex, setHistoryIndex] = useState(-1);
483
+
484
+ function addLog(msg, kind, stateSnapshot, calcSnapshot) {
485
+ setHistory(h => [...h, { msg, kind, stateSnapshot, calcSnapshot }]);
486
+ }
487
+
488
+ const displayedState = historyIndex >= 0 ? history[historyIndex].stateSnapshot : state;
489
+
490
+ const goBack = useCallback(() => {
491
+ setHistoryIndex(i => {
492
+ const next = i === -1 ? history.length - 1 : Math.max(0, i - 1);
493
+ setCalc(history[next].calcSnapshot);
494
+ return next;
495
+ });
496
+ }, [history]);
497
+
498
+ const goForward = useCallback(() => {
499
+ setHistoryIndex(i => {
500
+ const next = i === history.length - 1 ? -1 : Math.min(history.length - 1, i + 1);
501
+ if (next >= 0) setCalc(history[next].calcSnapshot);
502
+ else setCalc(null);
503
+ return next;
504
+ });
505
+ }, [history]);
506
+
507
+ const doOffer = useCallback(() => {
508
+ const val = input.trim();
509
+ if (!val) return;
510
+
511
+ setState(prev => {
512
+ const { buf, live, pIdx, cIdx } = prev;
513
+ const size = pIdx - cIdx;
514
+ const slot = pIdx & MASK;
515
+ const snap = { op: "offer", pIdx, cIdx, size, slot };
516
+ setCalc(snap);
517
+
518
+ if (size >= CAP) {
519
+ addLog(`offer("${val}") → false [size=${size} FULL]`, "fail", prev, snap);
520
+ setHi({ slots: [slot], color: COLOR.fail });
521
+ setHistoryIndex(-1);
522
+ return prev;
523
+ }
524
+
525
+ // Producer: write buf[slot], then release pIdx (ordering matters)
526
+ const newBuf = [...buf]; newBuf[slot] = val;
527
+ const newLive = [...live]; newLive[slot] = true;
528
+ const newState = { buf: newBuf, live: newLive, pIdx: pIdx + 1, cIdx };
529
+ addLog(
530
+ `offer("${val}") → true [slot ${slot} pIdx: ${pIdx}→${pIdx + 1}]`,
531
+ "write", newState, snap,
532
+ );
533
+ setHi({ slots: [slot], color: COLOR.write });
534
+ if (val.length === 1 && val >= "A" && val < "Z")
535
+ setInput(String.fromCharCode(val.charCodeAt(0) + 1));
536
+ setHistoryIndex(-1);
537
+ return newState;
538
+ });
539
+ }, [input]);
540
+
541
+ const doTake = useCallback(() => {
542
+ setState(prev => {
543
+ const { buf, live, pIdx, cIdx } = prev;
544
+ const size = pIdx - cIdx;
545
+ const slot = cIdx & MASK;
546
+ const element = buf[slot]; // read BEFORE CAS
547
+
548
+ const snap = { op: "take", cIdx, pIdx, size, slot, element };
549
+ setCalc(snap);
550
+
551
+ if (size === 0) {
552
+ addLog(`take() → null [pIdx=${pIdx} cIdx=${cIdx} EMPTY]`, "fail", prev, snap);
553
+ setHi({ slots: [slot], color: COLOR.fail });
554
+ setHistoryIndex(-1);
555
+ return prev;
556
+ }
557
+
558
+ // CAS succeeds (single-threaded sim). buf[slot] is NOT cleared — mark stale only.
559
+ const newLive = [...live]; newLive[slot] = false;
560
+ const newState = { buf, live: newLive, pIdx, cIdx: cIdx + 1 }; // buf unchanged!
561
+ addLog(
562
+ `take() → "${element}" [slot ${slot} cIdx: ${cIdx}→${cIdx + 1} buf[${slot}] kept]`,
563
+ "read", newState, snap,
564
+ );
565
+ setHi({ slots: [slot], color: COLOR.read });
566
+ setHistoryIndex(-1);
567
+ return newState;
568
+ });
569
+ }, []);
570
+
571
+ const doReset = useCallback(() => {
572
+ setState(initState());
573
+ setInput("A");
574
+ setHistory([{ msg: "initial state", kind: "neutral", stateSnapshot: initState(), calcSnapshot: null }]);
575
+ setHi({ slots: null, color: null });
576
+ setCalc(null);
577
+ setHistoryIndex(-1);
578
+ }, []);
579
+
580
+ const s = {
581
+ wrap: {
582
+ fontFamily: "sans-serif",
583
+ border: "1px solid #e0ded6", borderRadius: 12,
584
+ padding: "16px 16px 12px", background: "#fafaf8",
585
+ maxWidth: 680, margin: "1.5rem auto",
586
+ },
587
+ heading: {
588
+ fontSize: 13, fontWeight: 600, letterSpacing: "0.04em",
589
+ textTransform: "uppercase", color: "#888",
590
+ marginBottom: 12, textAlign: "center",
591
+ },
592
+ controls: {
593
+ display: "flex", alignItems: "center", gap: 8,
594
+ justifyContent: "center", flexWrap: "wrap", marginBottom: 6,
595
+ },
596
+ input: {
597
+ padding: "6px 10px", borderRadius: 8,
598
+ border: "1px solid #d4d2c8", background: "#fff",
599
+ fontSize: 13, fontFamily: "monospace", width: 110,
600
+ outline: "none", color: "#333",
601
+ },
602
+ btnOffer: {
603
+ padding: "6px 18px", borderRadius: 8, border: "none",
604
+ background: COLOR.write, color: "#fff",
605
+ fontSize: 13, fontWeight: 600, cursor: "pointer",
606
+ },
607
+ btnTake: {
608
+ padding: "6px 18px", borderRadius: 8, border: "none",
609
+ background: COLOR.read, color: "#fff",
610
+ fontSize: 13, fontWeight: 600, cursor: "pointer",
611
+ },
612
+ btnReset: {
613
+ padding: "6px 14px", borderRadius: 8,
614
+ border: "1px solid #d4d2c8", background: "transparent",
615
+ color: "#888", fontSize: 13, cursor: "pointer",
616
+ },
617
+ btnNav: (disabled) => ({
618
+ padding: "6px 12px", borderRadius: 8,
619
+ border: `1px solid ${disabled ? "#e8e6df" : "#d4d2c8"}`, background: "transparent",
620
+ color: disabled ? "#ccc" : "#888", fontSize: 13,
621
+ cursor: disabled ? "default" : "pointer", opacity: disabled ? 0.5 : 1,
622
+ }),
623
+ sectionLabel: { fontSize: 11, color: "#bbb", marginBottom: 4, paddingLeft: 4 },
624
+ hint: {
625
+ fontSize: 11, textAlign: "center", color: "#aaa",
626
+ marginTop: 8, lineHeight: 1.6,
627
+ },
628
+ };
629
+
630
+ return (
631
+ <div style={s.wrap}>
632
+ <div style={s.heading}>SPMC Ring Buffer · Capacity 4</div>
633
+
634
+ <div style={s.controls}>
635
+ <input
636
+ style={s.input} value={input} maxLength={6} placeholder='e.g. "A"'
637
+ onChange={e => setInput(e.target.value)}
638
+ onKeyDown={e => e.key === "Enter" && doOffer()}
639
+ />
640
+ <button style={s.btnOffer} onClick={doOffer}>Offer →</button>
641
+ <button style={s.btnTake} onClick={doTake}>← Take</button>
642
+ <button style={s.btnReset} onClick={doReset}>Reset</button>
643
+ <button style={s.btnNav(historyIndex === 0)} onClick={goBack} disabled={historyIndex === 0}>← Back</button>
644
+ <button style={s.btnNav(historyIndex === -1)} onClick={goForward} disabled={historyIndex === -1}>Forward →</button>
645
+ </div>
646
+
647
+ <RingDiagram
648
+ buf={displayedState.buf}
649
+ live={displayedState.live}
650
+ pIdx={displayedState.pIdx}
651
+ cIdx={displayedState.cIdx}
652
+ hiSlots={hi.slots}
653
+ hiColor={hi.color}
654
+ />
655
+
656
+ <div style={{ marginTop: 8 }}>
657
+ <div style={s.sectionLabel}>Step summary</div>
658
+ <StepDescription calc={calc} />
659
+ </div>
660
+
661
+ <div style={{ marginTop: 8 }}>
662
+ <div style={s.sectionLabel}>Algorithm trace</div>
663
+ <CalcPanel calc={calc} />
664
+ </div>
665
+
666
+ <div style={{ marginTop: 8 }}>
667
+ <div style={s.sectionLabel}>Operation history</div>
668
+ <HistoryLog entries={history} currentIndex={historyIndex} />
669
+ </div>
670
+
671
+ <div style={s.hint}>
672
+ Type any label ·{" "}
673
+ <strong style={{ color: COLOR.write }}>Offer</strong> to enqueue (single producer) ·{" "}
674
+ <strong style={{ color: COLOR.read }}>Take</strong> to dequeue (any consumer, CAS) ·{" "}
675
+ <strong style={{ color: COLOR.stale }}>stale</strong> slots show why consumers read before CAS ·
676
+ use Back / Forward to replay any step
677
+ </div>
678
+ </div>
679
+ );
680
+ }