Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .claude/skills/duplo-extension-dev/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ required/optional), a **Result** field table (name · type), the **provisioning
**detail view's shape** — the **lifecycle phases** this resource goes through (label + the status/result field that
drives each; [19-detail-page](reference/19-detail-page.md) → "Deriving phases") and the **Result tabs** (Overview at
minimum; name each further tab; [17-custom-result-views](reference/17-custom-result-views.md)) — and a short
**user-experience** walkthrough: left-nav placement, the add form, the list, the detail page, and what provisioning
**user-experience** walkthrough: left-nav placement, the add form (with the AI-use disclosure in Agent mode), the list, the detail page, and what provisioning
does end-to-end. Do this per resource for multi-resource extensions.

**Where the answers come from / how to ask:**
Expand Down Expand Up @@ -209,7 +209,7 @@ Adapt inside `extensions/<name>/` — rename `HelloWorld`→`<Name>` consistentl
`extensions/<feature>-<resource>` (== manifest `restSegment`; a bare leaf 404s).** Rename every FE `Hello`/`hw`
identifier (files, classes, selectors, routes, and a **unique** `federation.config.js` `name`) per the rename table
in [02](reference/02-authoring-guide.md#frontend). List = `searchable-datatable` with the filter + workspace-refresh
wiring; Add/Edit = `panel-form-accordion` + `form-field`; Detail = the **Template-G shell the scaffold already
wiring; Add/Edit = `panel-form-accordion` + `form-field`; **Agent mode: every Add/Edit form ends with `<app-ai-disclosure />`** (ISO 42001; the scaffold carries it, [14](reference/14-forms-and-wizards.md#ai-use-disclosure-agent-mode)); Detail = the **Template-G shell the scaffold already
carries** — keep `shared/` as copied and **rewrite the two things that are the resource's own**: the `phases()`
computed (your lifecycle ladder, per mode) and the Result strip's Overview tiles + any further tabs
([19](reference/19-detail-page.md), [17](reference/17-custom-result-views.md)). The federation `shared` block stays a
Expand All @@ -233,11 +233,11 @@ status", the "Needs your input" phase) exists **only in Agent mode** ([19](refer
3. add an `IDuploExtension` whose `Configure` calls `builder.Services.AddHostedService<<Name>Worker>();` — hot-load
starts the worker directly; `Configure` is what the boot-time replay uses ([10](reference/10-sdk-api.md) Gotchas).
4. **delete** the `skills/` dir and ship **no** `skills`/`skillMappings` in the manifest.
5. **FE:** remove the ticket UI, drop the "Needs your input" phase, compute the Worker ladder from `workerState`
5. **FE:** remove the ticket UI and the `<app-ai-disclosure />` (Agent-only), drop the "Needs your input" phase, compute the Worker ladder from `workerState`
(copy `samples/worker-compute`'s `phases()`), remove `track()`/`ticketName()` and the list-row "Track Provisioning"
item, label the add button **"Create"** (not "Provision").
- **Agent**: keep `skills/provision-*/SKILL.md` (+ `provision.sh`, write to the OWN route) and the manifest
`skills` + `skillMappings`; keep the ticket UI. **Deprovision seam (if it creates real infra):** the platform reuses the
`skills` + `skillMappings`; keep the ticket UI and the `<app-ai-disclosure />` on every Add/Edit form. **Deprovision seam (if it creates real infra):** the platform reuses the
SAME ticket and sends the generic teardown message — there is **no separate deprovision skill mapping**, so the
provision `SKILL.md` needs a **Deprovision** section + an idempotent `deprovision.sh`, or a second skill in the same
`skillNames` ([11](reference/11-deprovisioning.md)). Once your skill POSTs `DeProvisioned`, the platform hard-deletes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,12 @@ a plain `npm install` just works from a fresh clone. Two schemes coexist:

- **The samples** share one copy at repo-root `packages/`, since they never leave this repo:
```json
"@duplocloud-internal/ng-common-lib": "file:../../../packages/duplocloud-internal-ng-common-lib-0.2.0.tgz",
"@duplocloud-internal/ng-common-lib": "file:../../../packages/duplocloud-internal-ng-common-lib-0.4.0.tgz",
```
- **The skill template** (`templates/helloworld/frontend/`) keeps its **own** copy under `vendor/`, because it
gets copied out to `extensions/<name>/` or a provisioning workdir and must stay self-contained:
```json
"@duplocloud-internal/ng-common-lib": "file:vendor/duplocloud-internal-ng-common-lib-0.2.0.tgz",
"@duplocloud-internal/ng-common-lib": "file:vendor/duplocloud-internal-ng-common-lib-0.4.0.tgz",
```

Scaffolding from the template carries the tarball and the specifier with it — keep both when you copy.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ Import `SharedFormsModule` and use:
The single-page create form is the `panel-form-accordion` 3-column layout — see
[02-authoring-guide](02-authoring-guide.md) and `samples/helloworld`.

## AI-use disclosure (Agent mode)

DuploCloud is ISO 42001 certified: a form whose submit hands work to an AI agent (Agent mode — the resource maps a
skill in `manifest.skillMappings`) must say so at the point of use. Every Agent-mode Add/Edit form renders the
library's `<app-ai-disclosure />` (import `AiDisclosureComponent` from `@duplocloud-internal/ng-common-lib`):

- **Single-page form** — last element inside `.form-container`, under the Cancel/Provision row (see `samples/helloworld`).
- **Wizard** — pass `[aiDisclosure]="true"` to the copied `wizard-stepper`; it renders under the footer on every step.
- **Modal form** — inside `.modal-footer`, above the buttons.

It renders "This platform uses AI. See documentation for more details."; a deployment can change the text and link in
its config (`text.aiDisclosure`, `links.aiDisclosure`). Don't hardcode the wording or set `defaultText`. Worker,
Passthrough and No-provision forms do **not** carry it.

## Multi-step wizards — there is NO library stepper

The library ships **no stepper/wizard component**, and the host's own multi-step forms (AppService, etc.) hand-roll
Expand Down Expand Up @@ -121,6 +135,8 @@ prototype:
omits a step defeats confirm-before-provision.
- **Contextual error fallback**: `extractErrorMessage(err) || 'Could not create the <resource>.'` — name
the action that failed.
- **AI-use disclosure** — Agent-mode wizards pass `[aiDisclosure]="true"` to the stepper
([above](#ai-use-disclosure-agent-mode)).

## Dynamic fields from a remote schema

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ Deployed phase does exactly this.
### Ladders by provisioning mode — and the ticket UI that goes with them

Only **Agent** mode has a provisioning ticket. So only Agent-mode views render the header **Ask agent**
button, the rail's **Track status** (`[trackable]="true"`) and a **"Needs your input"** phase; they also
button, the rail's **Track status** (`[trackable]="true"`) and a **"Needs your input"** phase; their Add/Edit forms carry the `<app-ai-disclosure />` ([14](14-forms-and-wizards.md#ai-use-disclosure-agent-mode)); they also
keep `track()`/`ticketName()` and the list-row "Track Provisioning" item. Worker, Passthrough and
No-provision views omit all of those, use the ladders below, and label the add button **"Create"**
(not "Provision"). Opt-in Ask AI sessions ([16-ask-ai](16-ask-ai.md)) are user-created and work in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Everything the platform UI library exports, what each piece is for, how to impor
inside an extension remote. Check here before hand-rolling a table, form control, viewer or modal — the
platform almost certainly ships it already, and using it is what makes your pages match the suite.

Written against **0.3.0**. The source of truth is `portal/common-lib/public-api.ts` in duplo-ui; exact
Written against **0.4.0**. The source of truth is `portal/common-lib/public-api.ts` in duplo-ui; exact
signatures are in the installed package — see [Checking an exact API](#checking-an-exact-api).

## Rules
Expand Down Expand Up @@ -32,6 +32,7 @@ signatures are in the installed package — see [Checking an exact API](#checkin
| Row actions beyond Add / Edit | `searchable-datatable` `[extraActions]` | [table slots](#table-slots-filters-actions-alert) |
| A short child list inside a detail tab | `app-expandable-list` | [expandable list](#expandable-list) |
| A form field, validator or error summary | `SharedFormsModule` | [14-forms-and-wizards](14-forms-and-wizards.md) |
| The AI-use disclosure on an Agent-mode form | `app-ai-disclosure` | [14-forms-and-wizards](14-forms-and-wizards.md#ai-use-disclosure-agent-mode) |
| The real reason an API call failed | `extractErrorMessage`, `[form-group-errors]` | [15-error-handling](15-error-handling.md) |
| A delete / deprovision confirmation | `DeleteConfirmationModalService.openGeneric` | [confirm dialogs](#confirm-dialogs) |
| A yes/no confirmation | `ConfirmationModalService.openWithMessage` | [confirm dialogs](#confirm-dialogs) |
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@angular/material": "^22.1.0",
"@angular/platform-browser": "^22.1.0",
"@angular/router": "^22.1.0",
"@duplocloud-internal/ng-common-lib": "file:vendor/duplocloud-internal-ng-common-lib-0.3.0.tgz",
"@duplocloud-internal/ng-common-lib": "file:vendor/duplocloud-internal-ng-common-lib-0.4.0.tgz",
"@ng-bootstrap/ng-bootstrap": "^21.0.0",
"@ng-select/ng-select": "^23.5.1",
"@ngbracket/ngx-layout": "^22.0.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, OnInit, inject, signal, viewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { FormGroupErrorsComponent, SharedFormsModule } from '@duplocloud-internal/ng-common-lib';
import { AiDisclosureComponent, FormGroupErrorsComponent, SharedFormsModule } from '@duplocloud-internal/ng-common-lib';
import { HelloService } from '../hello.service';

// Add/Edit form in the platform's 3-column `panel-form-accordion` layout: title+description (left), inputs
Expand All @@ -16,7 +16,7 @@ import { HelloService } from '../hello.service';
// SharedFormsModule re-exports FormsModule plus form-field and every validation directive used here.
@Component({
selector: 'hw-add',
imports: [SharedFormsModule],
imports: [SharedFormsModule, AiDisclosureComponent],
styles: [`
:host { display: block; }
.panel-form-accordion { background: #fff; padding: 1.25rem 0 1rem 1.5rem; }
Expand Down Expand Up @@ -73,6 +73,8 @@ import { HelloService } from '../hello.service';
{{ isEdit ? 'Save' : 'Provision' }}
</button>
</div>
<!-- Agent mode only (ISO 42001): remove with the ticket UI when converting to Worker/Passthrough. -->
<app-ai-disclosure class="mt-1" />
</div>
</form>
}
Expand Down
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion docs/UPGRADING-ng-common-lib.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Upgrading an extension's UI library: the vendored `@duplocloud-internal/ng-common-lib` tarball

For extensions built from this dev-kit (or from the `duplo-extension-dev` skill template) that vendor the UI
library as a tarball. The current line is 0.2.0 (Angular 22); the notes below contrast it with the 0.1.x line.
library as a tarball. The current line is 0.4.0 (Angular 22); the notes below contrast it with the 0.1.x line.

> **This is not a drop-in upgrade.** 0.2.0 is compiled against **Angular 22** and ships as ESM
> (`fesm2022`, `"type": "module"`); its peer range is `@angular/core@^22.1.0`. It is meant to be consumed by
Expand Down
Binary file not shown.
Binary file not shown.
8 changes: 4 additions & 4 deletions samples/helloworld/frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion samples/helloworld/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@angular/material": "^22.1.0",
"@angular/platform-browser": "^22.1.0",
"@angular/router": "^22.1.0",
"@duplocloud-internal/ng-common-lib": "file:../../../packages/duplocloud-internal-ng-common-lib-0.3.0.tgz",
"@duplocloud-internal/ng-common-lib": "file:../../../packages/duplocloud-internal-ng-common-lib-0.4.0.tgz",
"@ng-bootstrap/ng-bootstrap": "^21.0.0",
"@ng-select/ng-select": "^23.5.1",
"@ngbracket/ngx-layout": "^22.0.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, OnInit, inject, signal, viewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { FormGroupErrorsComponent, SharedFormsModule } from '@duplocloud-internal/ng-common-lib';
import { AiDisclosureComponent, FormGroupErrorsComponent, SharedFormsModule } from '@duplocloud-internal/ng-common-lib';
import { HelloService } from '../hello.service';

// Add/Edit form in the platform's 3-column `panel-form-accordion` layout: title+description (left), inputs
Expand All @@ -16,7 +16,7 @@ import { HelloService } from '../hello.service';
// SharedFormsModule re-exports FormsModule plus form-field and every validation directive used here.
@Component({
selector: 'hw-add',
imports: [SharedFormsModule],
imports: [SharedFormsModule, AiDisclosureComponent],
styles: [`
:host { display: block; }
.panel-form-accordion { background: #fff; padding: 1.25rem 0 1rem 1.5rem; }
Expand Down Expand Up @@ -73,6 +73,8 @@ import { HelloService } from '../hello.service';
{{ isEdit ? 'Save' : 'Provision' }}
</button>
</div>
<!-- Agent mode only (ISO 42001): remove with the ticket UI when converting to Worker/Passthrough. -->
<app-ai-disclosure class="mt-1" />
</div>
</form>
}
Expand Down
8 changes: 4 additions & 4 deletions samples/network-stack/frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion samples/network-stack/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@angular/material": "^22.1.0",
"@angular/platform-browser": "^22.1.0",
"@angular/router": "^22.1.0",
"@duplocloud-internal/ng-common-lib": "file:../../../packages/duplocloud-internal-ng-common-lib-0.3.0.tgz",
"@duplocloud-internal/ng-common-lib": "file:../../../packages/duplocloud-internal-ng-common-lib-0.4.0.tgz",
"@ng-bootstrap/ng-bootstrap": "^21.0.0",
"@ng-select/ng-select": "^23.5.1",
"@ngbracket/ngx-layout": "^22.0.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { WizardStep, WizardStepperComponent } from '../wizard/wizard-stepper.com
<form #f="ngForm" class="form form-vertical" (ngSubmit)="f.valid && finish()">
<duplo-wizard-stepper
[title]="isEdit ? 'Edit Network Stack' : 'Create Network Stack'"
[aiDisclosure]="true"
subtitle="A VPC with subnets and a security group, provisioned by terraform in the background — the view page tracks the modules live and offers on-demand Plan/Apply."
[steps]="steps"
[activeIndex]="activeIndex()"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Component, computed, input, output } from '@angular/core';
import { AiDisclosureComponent } from '@duplocloud-internal/ng-common-lib';

export interface WizardStep {
key: string;
Expand All @@ -23,6 +24,7 @@ export interface WizardStep {
*/
@Component({
selector: 'duplo-wizard-stepper',
imports: [AiDisclosureComponent],
styles: [`
:host { display: block; }
/* One centered card: title, stepper, body and footer share a single width so nothing drifts apart.
Expand Down Expand Up @@ -105,6 +107,9 @@ export interface WizardStep {
}
</div>
</div>
@if (aiDisclosure()) {
<app-ai-disclosure class="pb-1" />
}
</div>
`,
})
Expand All @@ -115,6 +120,8 @@ export class WizardStepperComponent {
readonly nextDisabled = input(false);
readonly saving = input(false);
readonly finishLabel = input('Create');
/** Agent-mode forms only (ISO 42001); the stepper is shared with Worker-mode extensions. */
readonly aiDisclosure = input(false);
readonly error = input('');
/** Page heading rendered inside the card (never wrap the stepper in another card to fake one). */
readonly title = input('');
Expand Down
8 changes: 4 additions & 4 deletions samples/parent-child/frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading