@zio.dev/zio-blocks 0.0.33 → 0.0.55

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