1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
//! The PARTS-LIBRARY document lane — the one place the app rewrites a
//! parts-library entry from a part document, and the one place it writes a part
//! document back to the file it came from.
//!
//! Two callers ride it: the update-components batch
//! ([`crate::panels::update_components`]), which pulls a changed source file
//! into every instance, and the BOM's part-attribute edits
//! ([`crate::panels::bom`]), which change a part document in place and must
//! reach that part's file. A second copy of either rule is exactly the drift
//! this module exists to prevent.
//!
//! # The refresh lane — document transport, not a UI-side kernel call
//!
//! The kernel's `refresh_library_entry` mutates the RUNNER-side thread-local
//! library (the thread/worker that runs `execute_history`); calling it from the
//! UI thread under the Thread/Worker runner would mutate a cold store nobody
//! executes against (it only *looks* right under the test-time Inline runner).
//! A refresh therefore rides the DOCUMENT: rewrite the assembly's
//! `partsLibrary[part]` entry — new `document`, a fresh content-hash
//! `sourceSignature`, and a BLANKED `snapshot` when the new document builds a
//! different part — and reload.
//! `set_history_json` resets the runner (clearing its resident library), the
//! next run ingests the block, and the empty snapshot forces the kernel's
//! designed SELF-HEAL lane: re-execute the embedded document, re-snapshot,
//! rebuild every instance, re-solve. Correct under every runner.
//!
//! # WRITE-THROUGH — saving an edited part back to its SOURCE file
//!
//! Changing a part document in the assembly is an IN-MEMORY update; on its own
//! it persists only if the user then saves the ASSEMBLY, leaving the part's
//! `.nbrep` silently behind (and — because [`crate::panels::update_components`]
//! compares the store's content against the entry's `sourceSignature` — badging
//! the component "outdated" against a file it is actually NEWER than).
//! [`write_through`] writes the edited part document back to the `sourceKey` it
//! came from, so the entry's signature and the file agree and the badge stays
//! dark. Six lanes:
//!
//! * **embedded-only** (`sourceKey: ""`): no file exists to write back to.
//! Skipped SILENTLY. This used to be EVERY STEP-imported part (build-spec
//! §3.5); a STEP import now writes its unique parts to the store and stamps a
//! real key, so the empty key is what a part gets when there was no store to
//! write to — a headless import, or a write that failed.
//! * **source gone** (the file was deleted or renamed since insertion): NOT
//! recreated — resurrecting a file the user removed is a worse surprise than
//! a note. The assembly keeps the edit; a notice says the file was not
//! updated.
//! * **source moved on** (the file was edited + saved elsewhere since the
//! assembly last synced with it — the very case the outdated badge catches):
//! NOT overwritten. Clobbering it would destroy work that lives nowhere
//! else; the assembly keeps the edit, the notice names the file, and the
//! badge (now genuinely divergent) stays lit.
//! * **family member** (the file carries a `familySource` stamp): NOT
//! written. A member is generated from its family's table, so an edit
//! written into it would be a hand edit the next Generate undoes; the
//! assembly keeps the edit and a notice says where the change belongs.
//! * **write failed**: the edit still lands in the assembly — losing it is the
//! worst outcome, a stale file is recoverable — with a notice carrying the
//! backend's error. (A WRITE-BEHIND backend can also fail LATER; that lands
//! in `ModelStore::take_persistence_errors`, which the shell already drains
//! into the toast overlay every frame.)
//! * **written**: a notice naming the part AND the file, so the side effect on
//! a document shared by other assemblies is visible rather than hidden.
use crateModelStore;
use ;
/// Rewrite `partsLibrary[part_name]` in a parsed assembly document with a NEW
/// part document text: the embedded `document`, a fresh [`document_signature`],
/// and — when the new document builds differently — a BLANKED `snapshot`. The
/// blank snapshot is the heal trigger: the entry's resident `dirty` flag cannot
/// ride the document (serde-skipped), but an unreadable snapshot routes the
/// ACOMP feature into the SAME self-heal lane (re-execute `document`,
/// re-snapshot, rebuild every instance, re-solve).
///
/// A new version that builds the SAME part (`brep_kernel::same_build`: only
/// its symbol, pads or BOM attributes moved) keeps the snapshot, which still
/// describes it. Blanking it there rebuilt the part for an edit that changed no
/// geometry: 24 s on a herringbone gear whose reload from its snapshot takes
/// 0.12 s.
///
/// The ONE "refresh a library entry through the document" step (the
/// document-transport lane in this module's header). The caller reloads the
/// rewritten document (`set_history_json`) once — batch callers rewrite every
/// entry first, then reload.
///
/// Returns what the user should look at afterwards, one line each: the sheet
/// components the refresh could not keep bound, and the point renames it
/// carried across on POSITION rather than on evidence. Neither is a refusal —
/// the entry is rewritten and the rest of the sheet folds, because one dangling
/// instance costing the user an entire refresh is the worse outcome.
/// WRITE-THROUGH: save `edited` (the part document the caller just changed) back
/// to the store document it came from, so the file and the assembly's embedded
/// copy stay one thing — and the update-components badge, which compares
/// [`document_signature`] of the file against the entry's `sourceSignature`,
/// reads a freshly edited part as up-to-date instead of backwards.
///
/// `target` is the entry's `(sourceKey, sourceSignature)` as it stood BEFORE the
/// edit re-stamped the signature: the key names the file, the signature is the
/// content the assembly last SYNCED with that file. `None` for an EMBEDDED-ONLY
/// entry (an empty `sourceKey`) — there is no file to write back to.
///
/// The module header lists the lanes. EVERY refusal leaves the caller's
/// in-memory change standing and only pushes a notice: a stale file is
/// recoverable, a dropped edit is not.
pub
/// The ONE signature fn, re-exported from its engine-altitude home
/// ([`brep_render::engine_state::document_signature`]) so the app's writers —
/// the insert flow (`panels::file`), [`refresh_library_entry`], the
/// update-components comparison (`panels::update_components`) — and any
/// engine-side writer hash a part document identically. Kept on this path
/// because this module is the app's documented signature/refresh home.
pub use document_signature;