You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
skills/icp-cli/references/binding-generation.md:67-69 says bindgen's wrapper turns opt T into T | null and tells agents to "always use T | null". That holds for a plain opt, but not for a record field: an opt T field is an optional property that is undefined when empty. An agent following the rule writes if (r.value !== null), which is true for an empty field. The certified-variables helper had exactly this bug: it rejected a valid proof of absence (fixed in #407).
Verified against the @icp-sdk/bindgen 0.4.0 and 0.4.1 wrappers:
Candid position
Wrapper type
Empty value
argument, return value, vec element, variant payload: opt T
Correct the section in binding-generation.md.T | null applies to a plain opt. A record field is optional and undefined when empty. To send an absent field, omit it or pass undefined.
Recommend == null or ?? for "is it empty". It is correct in every position and version, so an agent doesn't need to know which position it is in.
Add an eval for the record-field case. The existing case ("Candid opt T representation in bindgen") covers only a plain return value, and its expected result !== null stays correct.
fix(generate): send present but falsy optional values as present icp-js-bindgen#197 (present but falsy optionals): until it ships, 0, false and "" in an optional record field or variant payload are sent as absent. No skill change is needed once it ships. If it is skipped, add a pitfall: send such values only in a plain opt argument, or avoid falsy values in optional fields.
fix(generate): convert opt opt record fields through their three states icp-js-bindgen#198 (opt opt record fields): once released, document f?: T | null with its three states (omitted, null for opt null, a value), pinned to that bindgen version. If it is skipped, the skill must say instead that opt opt record fields do not match their declared type (they decode to Some/None objects, and null cannot be sent), and point to the raw declarations for those fields.
fix(generate): fail when the actor class name is taken by another declaration icp-js-bindgen#201 (actor class name): once released, generating the actor files for a .did file named after one of its own types (governance.did with type Governance, swap.did with type Swap) fails with a hint to rename the file. Where the skill shows vendoring NNS or SNS interfaces, if anywhere, use a distinct file name (e.g. nns_governance.did). The same advice applies if it is skipped, since the class then merges with the type without an error.
docs: describe what the actor interface file declares icp-js-bindgen#203 (docs for --actor-interface-file): no skill change. It documents the .d.ts as a reference that leaves out the actor class, which is how caffeine-app (--actor-interface-file) and writing-motoko ("use the generated backend.d.ts as the source of truth") already use it; neither imports it.
skills/icp-cli/references/binding-generation.md:67-69says bindgen's wrapper turnsopt TintoT | nulland tells agents to "always useT | null". That holds for a plainopt, but not for a record field: anopt Tfield is an optional property that isundefinedwhen empty. An agent following the rule writesif (r.value !== null), which is true for an empty field. The certified-variables helper had exactly this bug: it rejected a valid proof of absence (fixed in #407).Verified against the
@icp-sdk/bindgen0.4.0 and 0.4.1 wrappers:vecelement, variant payload:opt TT | nullnullf : opt Tf?: Tundefined(field omitted)opt opt T(not in a record)Some<T | null> | NoneNoneNeeded regardless of the bindgen PRs
These hold on 0.4.1 and after dfinity/icp-js-bindgen#197, dfinity/icp-js-bindgen#198 and dfinity/icp-js-bindgen#201:
binding-generation.md.T | nullapplies to a plainopt. A record field is optional andundefinedwhen empty. To send an absent field, omit it or passundefined.== nullor??for "is it empty". It is correct in every position and version, so an agent doesn't need to know which position it is in.result !== nullstays correct.icp-cli. Reconsider canister-calls: one skill for calling canisters, JS/TS first #406 movesbinding-generation.md'soptsection intocanister-callslater; the move then carries the corrected text. Reconsider canister-calls: one skill for calling canisters, JS/TS first #406's own Candid↔TS table had the same gap (opt T→T | nullonly) and is corrected there, since it is the long-term home of the full mapping.After the bindgen fixes are released
fix(generate): send present but falsy optional values as present icp-js-bindgen#197 (present but falsy optionals): until it ships,
0,falseand""in an optional record field or variant payload are sent as absent. No skill change is needed once it ships. If it is skipped, add a pitfall: send such values only in a plainoptargument, or avoid falsy values in optional fields.fix(generate): convert opt opt record fields through their three states icp-js-bindgen#198 (
opt optrecord fields): once released, documentf?: T | nullwith its three states (omitted,nullforopt null, a value), pinned to that bindgen version. If it is skipped, the skill must say instead thatopt optrecord fields do not match their declared type (they decode toSome/Noneobjects, andnullcannot be sent), and point to the raw declarations for those fields.fix(generate): fail when the actor class name is taken by another declaration icp-js-bindgen#201 (actor class name): once released, generating the actor files for a
.didfile named after one of its own types (governance.didwithtype Governance,swap.didwithtype Swap) fails with a hint to rename the file. Where the skill shows vendoring NNS or SNS interfaces, if anywhere, use a distinct file name (e.g.nns_governance.did). The same advice applies if it is skipped, since the class then merges with the type without an error.docs: describe what the actor interface file declares icp-js-bindgen#203 (docs for
--actor-interface-file): no skill change. It documents the.d.tsas a reference that leaves out the actor class, which is howcaffeine-app(--actor-interface-file) andwriting-motoko("use the generatedbackend.d.tsas the source of truth") already use it; neither imports it.Inline anonymous variants borrow their type from an unrelated candid type name icp-js-bindgen#172 (proposed, no PR yet): if adopted, an inline unit variant becomes a string-literal union instead of a
Variant_<tags>or borrowed enum. No skill referencesVariant_<tags>today, so nothing breaks; the mapping in Reconsider canister-calls: one skill for calling canisters, JS/TS first #406 then gains that row.Until the bindgen PRs are decided, document only the first section.