Skip to content

Add the instruction display orchestrator and public API#1016

Open
lorisleiva wants to merge 1 commit into
06-25-add_interpolated-intent_and_fallback-list_render_modesfrom
06-25-add_the_instruction_display_orchestrator_and_public_api
Open

Add the instruction display orchestrator and public API#1016
lorisleiva wants to merge 1 commit into
06-25-add_interpolated-intent_and_fallback-list_render_modesfrom
06-25-add_the_instruction_display_orchestrator_and_public_api

Conversation

@lorisleiva

Copy link
Copy Markdown
Member

This PR completes the clear-signing display-text feature (sRFC 39) by assembling the value and render-mode layers into a public API. getInstructionDisplay parses a concrete instruction and resolves it into a human-readable display — an intent label, an interpolated sentence, and a structured fallback list — returning null when the instruction cannot be parsed; getInstructionDisplayFromParsedInstruction does the same from an already-parsed instruction. buildDisplayContext bridges a parsed instruction and its root into the single context threaded through the layer, resolving defined-type links by path against a linkable dictionary (correct even for links nested in types from other programs) and computing which members were surfaced through the provide/inject graph so whenInjected fields hide correctly when their value is presented elsewhere. @codama/dynamic-parsers now exports ParsedInstruction. The whole display layer is exported from the package, and a changeset records the minor bumps.

This PR completes the clear-signing display-text feature (sRFC 39) by assembling the value and render-mode layers into a public API. getInstructionDisplay parses a concrete instruction and resolves it into a human-readable display — an intent label, an interpolated sentence, and a structured fallback list — returning null when the instruction cannot be parsed; getInstructionDisplayFromParsedInstruction does the same from an already-parsed instruction. buildDisplayContext bridges a parsed instruction and its root into the single context threaded through the layer, resolving defined-type links by path against a linkable dictionary (correct even for links nested in types from other programs) and computing which members were surfaced through the provide/inject graph so whenInjected fields hide correctly when their value is presented elsewhere. @codama/dynamic-parsers now exports ParsedInstruction. The whole display layer is exported from the package, and a changeset records the minor bumps.
@changeset-bot

changeset-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: dda515d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@codama/dynamic-instructions Minor
@codama/dynamic-parsers Minor
@codama/dynamic-client Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Copy Markdown
Member Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@lorisleiva lorisleiva marked this pull request as ready for review June 25, 2026 14:39
@lorisleiva lorisleiva requested a review from mikhd June 25, 2026 14:40
* resolve from the correct location.
*/
export function resolveDisplayType(type: TypeNode, displayContext: DisplayContext): TypeNode {
export function resolveDisplayType(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we move resolveDisplayType from format-argument-value.ts file, since it's not connected to formatting? May be its own resolve-display-type.ts
What do you think?

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR finalizes the clear-signing “display text” layer for dynamic instructions by adding an orchestrator that turns a concrete instruction (raw or already-parsed) into a human-readable presentation (intent label, optional interpolated sentence, and a structured fallback list). It also expands the display context to support correct cross-program defined-type link resolution and correct whenInjected hiding via a consumed-member computation.

Changes:

  • Added a public display orchestration API: getInstructionDisplay and getInstructionDisplayFromParsedInstruction, plus buildDisplayContext.
  • Implemented consumedMemberNames computation to make whenInjected hiding depend on resolved provide/inject usage (online vs offline behavior).
  • Exported ParsedInstruction from @codama/dynamic-parsers and exported the full display layer from @codama/dynamic-instructions (with changeset + dependency updates).

Reviewed changes

Copilot reviewed 21 out of 22 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pnpm-lock.yaml Adds workspace link for @codama/dynamic-parsers dependency usage.
packages/dynamic-parsers/src/parsers.ts Exports ParsedInstruction(+accounts) types for downstream API usage.
packages/dynamic-instructions/package.json Adds @codama/dynamic-parsers dependency required by the new API.
packages/dynamic-instructions/src/index.ts Re-exports the full display layer as part of the public package API.
packages/dynamic-instructions/src/display/index.ts Adds new exports for orchestrator/context and consumed-member resolution.
packages/dynamic-instructions/src/display/types.ts Introduces InstructionDisplay, options, consumedMemberNames, and path-based defined-type resolution.
packages/dynamic-instructions/src/display/get-instruction-display.ts New public API entrypoints for generating instruction display output.
packages/dynamic-instructions/src/display/build-display-context.ts New context builder bridging parsed instruction + root, including link resolution + consumed-member computation.
packages/dynamic-instructions/src/display/resolve-consumed-members.ts New logic to compute which members were surfaced through provide/inject (for whenInjected).
packages/dynamic-instructions/src/display/list-fallback.ts Switches from instruction to instructionPath and from provides.has to consumedMemberNames.has for hiding.
packages/dynamic-instructions/src/display/interpolate-intent.ts Switches from instruction to instructionPath and passes correct owner paths to formatting.
packages/dynamic-instructions/src/display/format-argument-value.ts Adds owner-path-aware link following so nested links resolve against the correct program.
packages/dynamic-instructions/src/display/format-value.ts Adjusts context typing to allow pre-consumed-member context usage.
packages/dynamic-instructions/src/display/resolve-injected-value.ts Adjusts context typing to allow pre-consumed-member context usage.
packages/dynamic-instructions/test/test-utils.ts Updates test helpers for instructionPath, consumed-member plumbing, and new resolver/fetch helpers.
packages/dynamic-instructions/test/display/resolve-consumed-members.test.ts New test coverage for consumed-member computation across online/offline cases.
packages/dynamic-instructions/test/display/get-instruction-display.test.ts New end-to-end tests for the orchestrator across presentation tiers + cross-program link resolution.
packages/dynamic-instructions/test/display/build-display-context.test.ts New tests for context assembly (data/accounts/provides/link resolution/options).
packages/dynamic-instructions/test/display/list-fallback.test.ts Updates tests to use path-based context + consumed-member hiding semantics.
packages/dynamic-instructions/test/display/interpolate-intent.test.ts Updates tests to use path-based context.
packages/dynamic-instructions/test/display/format-argument-value.test.ts Updates tests for new formatArgumentValue(type, ownerPath, value, ctx) signature and resolver helper.
.changeset/every-bikes-rhyme.md Records minor bumps + feature description for the new clear-signing display API.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +55 to +64
/**
* Builds a {@link ResolveDefinedTypeFn} backed by a `LinkableDictionary` populated from the root.
* The caller supplies the full path to the link, which carries the program context the dictionary
* needs — including for links reached after following other links.
*/
function createDefinedTypeResolver(root: RootNode): ResolveDefinedTypeFn {
const linkables = new LinkableDictionary();
visit(root, getRecordLinkablesVisitor(linkables));
return linkPath => linkables.getPath(linkPath);
}
Comment on lines +58 to +64
/** Builds a {@link ResolveDefinedTypeFn} that resolves the given defined types by name. */
export function mockResolveDefinedType(...definedTypes: DefinedTypeNode[]): ResolveDefinedTypeFn {
const byName = new Map(definedTypes.map(definedType => [definedType.name, definedType]));
return linkPath => {
const definedType = byName.get(getLastNodeFromPath(linkPath).name);
return definedType ? ([definedType] as NodePath<DefinedTypeNode>) : undefined;
};
[...displayContext.instructionPath, argument],
displayContext,
);
if (isNode(type, 'numberTypeNode') && type.display?.kind === 'amountNumberDisplayNode') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Is there a real-world scenario when we could have something like this? (numberTypeNode with display inside the structFieldType)

structTypeNode([
  structFieldTypeNode({
    name: 'amount',
    type: numberTypeNode('u64', 'le', {
      display: amountNumberDisplayNode({ decimals: injectedValueNode({ key: 'decimals' }) }),
    }),
  }),
]);

And we would need to traverse each key? Something like this:

if (argument.display?.flatten && isNode(resolved.type, 'structTypeNode')) {
  resolved.type.fields.forEach(field => {
    const fieldType = resolveDisplayType(field.type, [...resolved.ownerPath, field], ctx).type;
    addAmountKeys(fieldType, keys);
  });
  return;
}

@mikhd mikhd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to add new display methods to README.md?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants