@jaimevalasek/aioson 1.3.0
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.
- package/CHANGELOG.md +456 -0
- package/CODE_OF_CONDUCT.md +12 -0
- package/CONTRIBUTING.md +13 -0
- package/LICENSE +21 -0
- package/README.md +254 -0
- package/bin/aioson.js +4 -0
- package/docs/en/cli-reference.md +398 -0
- package/docs/en/i18n.md +52 -0
- package/docs/en/json-schemas.md +41 -0
- package/docs/en/mcp.md +56 -0
- package/docs/en/parallel.md +82 -0
- package/docs/en/qa-browser.md +339 -0
- package/docs/en/release-flow.md +22 -0
- package/docs/en/release-notes-template.md +41 -0
- package/docs/en/release.md +28 -0
- package/docs/en/schemas/agent-prompt.schema.json +17 -0
- package/docs/en/schemas/agents.schema.json +32 -0
- package/docs/en/schemas/context-validate.schema.json +36 -0
- package/docs/en/schemas/doctor.schema.json +89 -0
- package/docs/en/schemas/error.schema.json +24 -0
- package/docs/en/schemas/i18n-add.schema.json +15 -0
- package/docs/en/schemas/index.json +116 -0
- package/docs/en/schemas/info.schema.json +39 -0
- package/docs/en/schemas/init.schema.json +48 -0
- package/docs/en/schemas/install.schema.json +60 -0
- package/docs/en/schemas/locale-apply.schema.json +30 -0
- package/docs/en/schemas/mcp-doctor.schema.json +95 -0
- package/docs/en/schemas/mcp-init.schema.json +122 -0
- package/docs/en/schemas/package-test.schema.json +24 -0
- package/docs/en/schemas/parallel-assign.schema.json +57 -0
- package/docs/en/schemas/parallel-doctor.schema.json +86 -0
- package/docs/en/schemas/parallel-init.schema.json +53 -0
- package/docs/en/schemas/parallel-status.schema.json +94 -0
- package/docs/en/schemas/setup-context.schema.json +39 -0
- package/docs/en/schemas/smoke.schema.json +23 -0
- package/docs/en/schemas/update.schema.json +48 -0
- package/docs/en/schemas/workflow-plan.schema.json +30 -0
- package/docs/en/web3.md +54 -0
- package/docs/pt/README.md +46 -0
- package/docs/pt/advisor-spec.md +335 -0
- package/docs/pt/agentes.md +453 -0
- package/docs/pt/cenarios.md +1230 -0
- package/docs/pt/clientes-ai.md +224 -0
- package/docs/pt/comandos-cli.md +511 -0
- package/docs/pt/genome-3.0-spec.md +296 -0
- package/docs/pt/guia-engineer.md +226 -0
- package/docs/pt/inicio-rapido.md +138 -0
- package/docs/pt/profiler-system.md +214 -0
- package/docs/pt/runtime-observability.md +72 -0
- package/docs/pt/squad-genoma.md +777 -0
- package/docs/pt/web3.md +797 -0
- package/docs/testing/genome-2.0-manual-regression.md +23 -0
- package/docs/testing/genome-2.0-matrix.md +36 -0
- package/docs/testing/genome-2.0-rollout.md +184 -0
- package/package.json +50 -0
- package/src/agents.js +56 -0
- package/src/cli.js +497 -0
- package/src/commands/agents.js +142 -0
- package/src/commands/cloud.js +1767 -0
- package/src/commands/config.js +90 -0
- package/src/commands/context-validate.js +91 -0
- package/src/commands/doctor.js +123 -0
- package/src/commands/genome-doctor.js +41 -0
- package/src/commands/genome-migrate.js +49 -0
- package/src/commands/i18n-add.js +56 -0
- package/src/commands/info.js +41 -0
- package/src/commands/init.js +75 -0
- package/src/commands/install.js +68 -0
- package/src/commands/locale-apply.js +51 -0
- package/src/commands/locale-diff.js +126 -0
- package/src/commands/mcp-doctor.js +406 -0
- package/src/commands/mcp-init.js +379 -0
- package/src/commands/package-e2e.js +273 -0
- package/src/commands/parallel-assign.js +403 -0
- package/src/commands/parallel-doctor.js +437 -0
- package/src/commands/parallel-init.js +249 -0
- package/src/commands/parallel-status.js +290 -0
- package/src/commands/qa-doctor.js +185 -0
- package/src/commands/qa-init.js +161 -0
- package/src/commands/qa-report.js +58 -0
- package/src/commands/qa-run.js +873 -0
- package/src/commands/qa-scan.js +337 -0
- package/src/commands/runtime.js +948 -0
- package/src/commands/scan-project.js +1107 -0
- package/src/commands/setup-context.js +650 -0
- package/src/commands/smoke.js +426 -0
- package/src/commands/squad-doctor.js +358 -0
- package/src/commands/squad-export.js +46 -0
- package/src/commands/squad-pipeline.js +97 -0
- package/src/commands/squad-repair-genomes.js +39 -0
- package/src/commands/squad-status.js +424 -0
- package/src/commands/squad-validate.js +230 -0
- package/src/commands/test-agents.js +194 -0
- package/src/commands/update.js +55 -0
- package/src/commands/workflow-next.js +594 -0
- package/src/commands/workflow-plan.js +108 -0
- package/src/constants.js +314 -0
- package/src/context-parse-reason.js +22 -0
- package/src/context-writer.js +150 -0
- package/src/context.js +217 -0
- package/src/detector.js +261 -0
- package/src/doctor.js +289 -0
- package/src/execution-gateway.js +461 -0
- package/src/genome-files.js +198 -0
- package/src/genome-format.js +442 -0
- package/src/genome-schema.js +215 -0
- package/src/genomes/bindings.js +281 -0
- package/src/genomes.js +467 -0
- package/src/i18n/index.js +103 -0
- package/src/i18n/messages/en.js +784 -0
- package/src/i18n/messages/es.js +718 -0
- package/src/i18n/messages/fr.js +725 -0
- package/src/i18n/messages/pt-BR.js +818 -0
- package/src/i18n/scaffold.js +64 -0
- package/src/installer.js +232 -0
- package/src/lib/genomes/compat.js +206 -0
- package/src/lib/genomes/migrate.js +90 -0
- package/src/lib/squads/genome-repair.js +49 -0
- package/src/locales.js +84 -0
- package/src/onboarding.js +305 -0
- package/src/parser.js +53 -0
- package/src/prompt-tool.js +20 -0
- package/src/qa-html-report.js +472 -0
- package/src/runtime-store.js +1527 -0
- package/src/squads/apply-genome.js +21 -0
- package/src/squads/genome-binding-service.js +154 -0
- package/src/updater.js +32 -0
- package/src/utils.js +46 -0
- package/src/version.js +50 -0
- package/template/.aioson/advisors/.gitkeep +1 -0
- package/template/.aioson/agents/analyst.md +225 -0
- package/template/.aioson/agents/architect.md +221 -0
- package/template/.aioson/agents/dev.md +201 -0
- package/template/.aioson/agents/discovery-design-doc.md +196 -0
- package/template/.aioson/agents/genoma.md +300 -0
- package/template/.aioson/agents/orchestrator.md +107 -0
- package/template/.aioson/agents/pm.md +89 -0
- package/template/.aioson/agents/product.md +361 -0
- package/template/.aioson/agents/profiler-enricher.md +266 -0
- package/template/.aioson/agents/profiler-forge.md +188 -0
- package/template/.aioson/agents/profiler-researcher.md +245 -0
- package/template/.aioson/agents/qa.md +344 -0
- package/template/.aioson/agents/setup.md +381 -0
- package/template/.aioson/agents/squad.md +837 -0
- package/template/.aioson/agents/ux-ui.md +416 -0
- package/template/.aioson/config.md +56 -0
- package/template/.aioson/context/.gitkeep +0 -0
- package/template/.aioson/context/parallel/.gitkeep +0 -0
- package/template/.aioson/context/spec.md.template +37 -0
- package/template/.aioson/genomas/.gitkeep +0 -0
- package/template/.aioson/locales/en/agents/analyst.md +214 -0
- package/template/.aioson/locales/en/agents/architect.md +210 -0
- package/template/.aioson/locales/en/agents/dev.md +187 -0
- package/template/.aioson/locales/en/agents/discovery-design-doc.md +27 -0
- package/template/.aioson/locales/en/agents/genoma.md +212 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +105 -0
- package/template/.aioson/locales/en/agents/pm.md +77 -0
- package/template/.aioson/locales/en/agents/product.md +310 -0
- package/template/.aioson/locales/en/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/en/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/en/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/en/agents/qa.md +214 -0
- package/template/.aioson/locales/en/agents/setup.md +342 -0
- package/template/.aioson/locales/en/agents/squad.md +247 -0
- package/template/.aioson/locales/en/agents/ux-ui.md +320 -0
- package/template/.aioson/locales/es/agents/analyst.md +203 -0
- package/template/.aioson/locales/es/agents/architect.md +208 -0
- package/template/.aioson/locales/es/agents/dev.md +183 -0
- package/template/.aioson/locales/es/agents/discovery-design-doc.md +19 -0
- package/template/.aioson/locales/es/agents/genoma.md +102 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +108 -0
- package/template/.aioson/locales/es/agents/pm.md +81 -0
- package/template/.aioson/locales/es/agents/product.md +310 -0
- package/template/.aioson/locales/es/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/es/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/es/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/es/agents/qa.md +163 -0
- package/template/.aioson/locales/es/agents/setup.md +347 -0
- package/template/.aioson/locales/es/agents/squad.md +247 -0
- package/template/.aioson/locales/es/agents/ux-ui.md +201 -0
- package/template/.aioson/locales/fr/agents/analyst.md +203 -0
- package/template/.aioson/locales/fr/agents/architect.md +208 -0
- package/template/.aioson/locales/fr/agents/dev.md +183 -0
- package/template/.aioson/locales/fr/agents/discovery-design-doc.md +19 -0
- package/template/.aioson/locales/fr/agents/genoma.md +102 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +108 -0
- package/template/.aioson/locales/fr/agents/pm.md +81 -0
- package/template/.aioson/locales/fr/agents/product.md +310 -0
- package/template/.aioson/locales/fr/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/fr/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/fr/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/fr/agents/qa.md +163 -0
- package/template/.aioson/locales/fr/agents/setup.md +347 -0
- package/template/.aioson/locales/fr/agents/squad.md +247 -0
- package/template/.aioson/locales/fr/agents/ux-ui.md +201 -0
- package/template/.aioson/locales/pt-BR/agents/analyst.md +217 -0
- package/template/.aioson/locales/pt-BR/agents/architect.md +213 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +198 -0
- package/template/.aioson/locales/pt-BR/agents/discovery-design-doc.md +198 -0
- package/template/.aioson/locales/pt-BR/agents/genoma.md +297 -0
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +108 -0
- package/template/.aioson/locales/pt-BR/agents/pm.md +81 -0
- package/template/.aioson/locales/pt-BR/agents/product.md +316 -0
- package/template/.aioson/locales/pt-BR/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/qa.md +217 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +371 -0
- package/template/.aioson/locales/pt-BR/agents/squad.md +772 -0
- package/template/.aioson/locales/pt-BR/agents/ux-ui.md +322 -0
- package/template/.aioson/mcp/servers.md +24 -0
- package/template/.aioson/profiler-reports/.gitkeep +1 -0
- package/template/.aioson/schemas/content-blueprint.schema.json +30 -0
- package/template/.aioson/schemas/genome-meta.schema.json +150 -0
- package/template/.aioson/schemas/genome.schema.json +115 -0
- package/template/.aioson/schemas/readiness.schema.json +27 -0
- package/template/.aioson/schemas/squad-blueprint.schema.json +172 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +276 -0
- package/template/.aioson/skills/dynamic/README.md +30 -0
- package/template/.aioson/skills/dynamic/cardano-docs.md +16 -0
- package/template/.aioson/skills/dynamic/ethereum-docs.md +17 -0
- package/template/.aioson/skills/dynamic/flux-ui-docs.md +13 -0
- package/template/.aioson/skills/dynamic/laravel-docs.md +41 -0
- package/template/.aioson/skills/dynamic/npm-packages.md +16 -0
- package/template/.aioson/skills/dynamic/solana-docs.md +16 -0
- package/template/.aioson/skills/references/premium-command-center-ui/master-application-prompt.md +79 -0
- package/template/.aioson/skills/references/premium-command-center-ui/operational-ux-playbook.md +253 -0
- package/template/.aioson/skills/references/premium-command-center-ui/quality-validation-checklist.md +82 -0
- package/template/.aioson/skills/references/premium-command-center-ui/visual-system-and-component-patterns.md +270 -0
- package/template/.aioson/skills/static/django-patterns.md +342 -0
- package/template/.aioson/skills/static/fastapi-patterns.md +344 -0
- package/template/.aioson/skills/static/filament-patterns.md +267 -0
- package/template/.aioson/skills/static/flux-ui-components.md +262 -0
- package/template/.aioson/skills/static/git-conventions.md +227 -0
- package/template/.aioson/skills/static/interface-design.md +372 -0
- package/template/.aioson/skills/static/jetstream-setup.md +200 -0
- package/template/.aioson/skills/static/laravel-conventions.md +491 -0
- package/template/.aioson/skills/static/nextjs-patterns.md +321 -0
- package/template/.aioson/skills/static/node-express-patterns.md +317 -0
- package/template/.aioson/skills/static/node-typescript-patterns.md +282 -0
- package/template/.aioson/skills/static/premium-command-center-ui.md +190 -0
- package/template/.aioson/skills/static/rails-conventions.md +307 -0
- package/template/.aioson/skills/static/react-motion-patterns.md +577 -0
- package/template/.aioson/skills/static/static-html-patterns.md +1935 -0
- package/template/.aioson/skills/static/tall-stack-patterns.md +286 -0
- package/template/.aioson/skills/static/ui-ux-modern.md +75 -0
- package/template/.aioson/skills/static/web3-cardano-patterns.md +337 -0
- package/template/.aioson/skills/static/web3-ethereum-patterns.md +310 -0
- package/template/.aioson/skills/static/web3-security-checklist.md +284 -0
- package/template/.aioson/skills/static/web3-solana-patterns.md +324 -0
- package/template/.aioson/squads/.artisan/.gitkeep +0 -0
- package/template/.aioson/squads/.gitkeep +0 -0
- package/template/.aioson/squads/memory.md +5 -0
- package/template/.aioson/tasks/squad-analyze.md +83 -0
- package/template/.aioson/tasks/squad-create.md +99 -0
- package/template/.aioson/tasks/squad-design.md +100 -0
- package/template/.aioson/tasks/squad-export.md +20 -0
- package/template/.aioson/tasks/squad-extend.md +68 -0
- package/template/.aioson/tasks/squad-pipeline.md +122 -0
- package/template/.aioson/tasks/squad-repair.md +85 -0
- package/template/.aioson/tasks/squad-validate.md +58 -0
- package/template/.aioson/templates/squads/content-basic/template.json +21 -0
- package/template/.aioson/templates/squads/media-channel/template.json +24 -0
- package/template/.aioson/templates/squads/research-analysis/template.json +22 -0
- package/template/.aioson/templates/squads/software-delivery/template.json +21 -0
- package/template/.claude/commands/aioson/analyst.md +5 -0
- package/template/.claude/commands/aioson/architect.md +5 -0
- package/template/.claude/commands/aioson/dev.md +5 -0
- package/template/.claude/commands/aioson/orchestrator.md +5 -0
- package/template/.claude/commands/aioson/pm.md +5 -0
- package/template/.claude/commands/aioson/qa.md +5 -0
- package/template/.claude/commands/aioson/setup.md +5 -0
- package/template/.claude/commands/aioson/ux-ui.md +5 -0
- package/template/.gemini/GEMINI.md +10 -0
- package/template/.gemini/commands/aios-analyst.toml +4 -0
- package/template/.gemini/commands/aios-architect.toml +7 -0
- package/template/.gemini/commands/aios-dev.toml +8 -0
- package/template/.gemini/commands/aios-discovery-design-doc.toml +4 -0
- package/template/.gemini/commands/aios-orchestrator.toml +8 -0
- package/template/.gemini/commands/aios-pm.toml +8 -0
- package/template/.gemini/commands/aios-product.toml +4 -0
- package/template/.gemini/commands/aios-qa.toml +6 -0
- package/template/.gemini/commands/aios-setup.toml +3 -0
- package/template/.gemini/commands/aios-ux-ui.toml +8 -0
- package/template/AGENTS.md +67 -0
- package/template/CLAUDE.md +31 -0
- package/template/OPENCODE.md +24 -0
- package/template/aioson-models.json +40 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# TALL Stack Patterns
|
|
2
|
+
|
|
3
|
+
> Tailwind + Alpine.js + Livewire + Laravel. Full-stack PHP monolith done right.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Architecture: what goes where
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Livewire → stateful server-side UI (forms, tables, modals, real-time updates)
|
|
11
|
+
Alpine.js → lightweight client interactivity (toggles, dropdowns, local UI state)
|
|
12
|
+
Tailwind → all styling — never write custom CSS for layout or spacing
|
|
13
|
+
Blade → templating, layouts, shared components
|
|
14
|
+
Flux UI → pre-built component library — always check here first
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Rule:** If Alpine.js logic exceeds ~20 lines, move the state to Livewire.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Livewire component structure
|
|
22
|
+
|
|
23
|
+
```php
|
|
24
|
+
// app/Livewire/Appointments/CreateForm.php
|
|
25
|
+
class CreateForm extends Component
|
|
26
|
+
{
|
|
27
|
+
public int $doctorId = 0;
|
|
28
|
+
public string $date = '';
|
|
29
|
+
public string $notes = '';
|
|
30
|
+
|
|
31
|
+
protected array $rules = [
|
|
32
|
+
'doctorId' => 'required|exists:doctors,id',
|
|
33
|
+
'date' => 'required|date|after:today',
|
|
34
|
+
'notes' => 'nullable|string|max:500',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// Real-time validation on field change
|
|
38
|
+
public function updated(string $field): void
|
|
39
|
+
{
|
|
40
|
+
$this->validateOnly($field);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public function submit(): void
|
|
44
|
+
{
|
|
45
|
+
$this->validate();
|
|
46
|
+
$appointment = (new CreateAppointmentAction)->execute(
|
|
47
|
+
auth()->user(),
|
|
48
|
+
AppointmentData::from($this->only(['doctorId', 'date', 'notes']))
|
|
49
|
+
);
|
|
50
|
+
$this->dispatch('appointment-created', id: $appointment->id);
|
|
51
|
+
$this->reset(['doctorId', 'date', 'notes']);
|
|
52
|
+
session()->flash('success', 'Appointment booked.');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public function render(): View
|
|
56
|
+
{
|
|
57
|
+
return view('livewire.appointments.create-form', [
|
|
58
|
+
'doctors' => Doctor::orderBy('name')->get(),
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Livewire — inter-component events
|
|
67
|
+
|
|
68
|
+
```php
|
|
69
|
+
// Child dispatches
|
|
70
|
+
$this->dispatch('appointment-created', id: $appointment->id);
|
|
71
|
+
|
|
72
|
+
// Parent listens
|
|
73
|
+
#[On('appointment-created')]
|
|
74
|
+
public function refresh(): void
|
|
75
|
+
{
|
|
76
|
+
$this->appointments = Appointment::withRelations()->latest()->paginate(20);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Target a specific component
|
|
80
|
+
$this->dispatch('refresh')->to(AppointmentList::class);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Livewire — lazy loading for expensive components
|
|
86
|
+
|
|
87
|
+
```html
|
|
88
|
+
<!-- Defer until visible in viewport -->
|
|
89
|
+
<livewire:dashboard-stats lazy />
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```php
|
|
93
|
+
// Component shows skeleton while loading
|
|
94
|
+
public function placeholder(): View
|
|
95
|
+
{
|
|
96
|
+
return view('livewire.placeholders.stats-skeleton');
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Alpine.js — keep it lightweight
|
|
103
|
+
|
|
104
|
+
Use Alpine for pure UI state that does not involve the server.
|
|
105
|
+
|
|
106
|
+
```html
|
|
107
|
+
<!-- Toggle visibility -->
|
|
108
|
+
<div x-data="{ open: false }" @click.outside="open = false">
|
|
109
|
+
<button @click="open = !open">Options</button>
|
|
110
|
+
<div x-show="open" x-transition>
|
|
111
|
+
<a href="#">Edit</a>
|
|
112
|
+
<a href="#">Delete</a>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<!-- Confirm before destructive Livewire action -->
|
|
117
|
+
<button
|
|
118
|
+
x-data
|
|
119
|
+
@click="if (confirm('Delete this appointment?')) $wire.delete({{ $appointment->id }})"
|
|
120
|
+
class="text-red-600 hover:underline"
|
|
121
|
+
>
|
|
122
|
+
Delete
|
|
123
|
+
</button>
|
|
124
|
+
|
|
125
|
+
<!-- Copy to clipboard -->
|
|
126
|
+
<div x-data="{ copied: false }">
|
|
127
|
+
<button @click="navigator.clipboard.writeText('{{ $link }}'); copied = true; setTimeout(() => copied = false, 2000)">
|
|
128
|
+
<span x-text="copied ? 'Copied!' : 'Copy link'"></span>
|
|
129
|
+
</button>
|
|
130
|
+
</div>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Tailwind — design system discipline
|
|
136
|
+
|
|
137
|
+
Always use Tailwind's spacing scale. Never use arbitrary values for standard spacing.
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<!-- WRONG -->
|
|
141
|
+
<div class="p-[17px] mt-[22px] text-[15px]">
|
|
142
|
+
|
|
143
|
+
<!-- RIGHT -->
|
|
144
|
+
<div class="p-4 mt-6 text-sm">
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Define tokens in `tailwind.config.js`:
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
module.exports = {
|
|
151
|
+
theme: {
|
|
152
|
+
extend: {
|
|
153
|
+
colors: {
|
|
154
|
+
brand: { DEFAULT: '#4F46E5', dark: '#4338CA' },
|
|
155
|
+
},
|
|
156
|
+
spacing: {
|
|
157
|
+
sidebar: '280px',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Flux UI — use before building custom
|
|
167
|
+
|
|
168
|
+
```html
|
|
169
|
+
<!-- Buttons -->
|
|
170
|
+
<flux:button variant="primary">Save</flux:button>
|
|
171
|
+
<flux:button variant="ghost" icon="trash">Delete</flux:button>
|
|
172
|
+
<flux:button variant="danger" wire:click="delete" wire:loading.attr="disabled">
|
|
173
|
+
<wire:loading wire:target="delete">Deleting...</wire:loading>
|
|
174
|
+
<wire:loading.remove wire:target="delete">Delete</wire:loading.remove>
|
|
175
|
+
</flux:button>
|
|
176
|
+
|
|
177
|
+
<!-- Form inputs -->
|
|
178
|
+
<flux:input wire:model.live="email" label="Email" type="email" />
|
|
179
|
+
<flux:textarea wire:model="notes" label="Notes" rows="4" />
|
|
180
|
+
<flux:select wire:model="doctorId" label="Doctor">
|
|
181
|
+
<flux:select.option value="">Select a doctor</flux:select.option>
|
|
182
|
+
@foreach ($doctors as $doctor)
|
|
183
|
+
<flux:select.option value="{{ $doctor->id }}">{{ $doctor->name }}</flux:select.option>
|
|
184
|
+
@endforeach
|
|
185
|
+
</flux:select>
|
|
186
|
+
|
|
187
|
+
<!-- Modal with confirmation -->
|
|
188
|
+
<flux:modal name="confirm-delete">
|
|
189
|
+
<flux:heading>Delete appointment?</flux:heading>
|
|
190
|
+
<flux:text>This action cannot be undone.</flux:text>
|
|
191
|
+
<div class="flex gap-2 mt-4">
|
|
192
|
+
<flux:button variant="danger" wire:click="delete">Delete</flux:button>
|
|
193
|
+
<flux:modal.close>Cancel</flux:modal.close>
|
|
194
|
+
</div>
|
|
195
|
+
</flux:modal>
|
|
196
|
+
<flux:modal.trigger name="confirm-delete">
|
|
197
|
+
<flux:button variant="ghost" icon="trash">Delete</flux:button>
|
|
198
|
+
</flux:modal.trigger>
|
|
199
|
+
|
|
200
|
+
<!-- Table -->
|
|
201
|
+
<flux:table>
|
|
202
|
+
<flux:columns>
|
|
203
|
+
<flux:column>Patient</flux:column>
|
|
204
|
+
<flux:column>Date</flux:column>
|
|
205
|
+
<flux:column>Status</flux:column>
|
|
206
|
+
</flux:columns>
|
|
207
|
+
<flux:rows>
|
|
208
|
+
@foreach ($appointments as $appointment)
|
|
209
|
+
<flux:row>
|
|
210
|
+
<flux:cell>{{ $appointment->patient->name }}</flux:cell>
|
|
211
|
+
<flux:cell>{{ $appointment->date->format('M d, Y H:i') }}</flux:cell>
|
|
212
|
+
<flux:cell><flux:badge>{{ $appointment->status }}</flux:badge></flux:cell>
|
|
213
|
+
</flux:row>
|
|
214
|
+
@endforeach
|
|
215
|
+
</flux:rows>
|
|
216
|
+
</flux:table>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Blade layout structure
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
resources/views/
|
|
225
|
+
components/
|
|
226
|
+
layouts/
|
|
227
|
+
app.blade.php ← authenticated shell (nav, sidebar)
|
|
228
|
+
guest.blade.php ← public shell (centered, minimal)
|
|
229
|
+
ui/
|
|
230
|
+
card.blade.php ← reusable card wrapper
|
|
231
|
+
empty-state.blade.php
|
|
232
|
+
page-header.blade.php
|
|
233
|
+
livewire/
|
|
234
|
+
appointments/
|
|
235
|
+
create-form.blade.php
|
|
236
|
+
list.blade.php
|
|
237
|
+
placeholders/
|
|
238
|
+
stats-skeleton.blade.php
|
|
239
|
+
pages/
|
|
240
|
+
appointments/
|
|
241
|
+
index.blade.php
|
|
242
|
+
show.blade.php
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Performance
|
|
248
|
+
|
|
249
|
+
```php
|
|
250
|
+
// Always paginate Livewire lists — never ->get() on unbounded queries
|
|
251
|
+
public function render(): View
|
|
252
|
+
{
|
|
253
|
+
return view('livewire.appointments.list', [
|
|
254
|
+
'appointments' => Appointment::withRelations()
|
|
255
|
+
->when($this->search, fn ($q) =>
|
|
256
|
+
$q->where('notes', 'like', "%{$this->search}%"))
|
|
257
|
+
->latest()
|
|
258
|
+
->paginate(20),
|
|
259
|
+
]);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Cache reference data with computed properties
|
|
263
|
+
public function getDoctorsProperty(): Collection
|
|
264
|
+
{
|
|
265
|
+
return Cache::remember('doctors.active', now()->addMinutes(5), fn () =>
|
|
266
|
+
Doctor::active()->orderBy('name')->get()
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## ALWAYS
|
|
274
|
+
- `wire:model.live` for real-time field validation
|
|
275
|
+
- `$this->validateOnly($field)` in `updated()`
|
|
276
|
+
- `#[On('event')]` for component communication
|
|
277
|
+
- `paginate()` for all list queries
|
|
278
|
+
- Flux UI components before custom Blade components
|
|
279
|
+
- `x-transition` for smooth Alpine.js reveals
|
|
280
|
+
|
|
281
|
+
## NEVER
|
|
282
|
+
- Database queries in Blade templates
|
|
283
|
+
- Alpine.js managing server state or API calls
|
|
284
|
+
- `->get()` on unbounded Eloquent queries in Livewire
|
|
285
|
+
- Custom CSS when a Tailwind utility exists
|
|
286
|
+
- Unguarded public Livewire properties (validate everything)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Modern UI/UX Guidelines
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
Ship interfaces that feel intentional, modern, and production-ready without over-designing beyond project scope.
|
|
5
|
+
|
|
6
|
+
## Core-Lite balance
|
|
7
|
+
- Follow AIOS Core quality discipline (explicit decisions, consistency, UX clarity).
|
|
8
|
+
- Keep AIOSON delivery lean (small scope, direct implementation guidance, low artifact overhead).
|
|
9
|
+
|
|
10
|
+
## Core principles
|
|
11
|
+
- Clarity before decoration: users should immediately understand the primary action.
|
|
12
|
+
- One visual direction per product area: typography, color, and component density must be consistent.
|
|
13
|
+
- Reuse before reinventing: prioritize existing stack UI libraries and primitives.
|
|
14
|
+
- Accessibility is default, not optional.
|
|
15
|
+
|
|
16
|
+
## Layout and hierarchy
|
|
17
|
+
- Define a spacing scale (for example 4/8/12/16/24/32) and keep it consistent.
|
|
18
|
+
- Keep a clear reading order:
|
|
19
|
+
1. Page intent
|
|
20
|
+
2. Primary action
|
|
21
|
+
3. Supporting data
|
|
22
|
+
- Prefer shallow nesting in cards and panels to reduce cognitive load.
|
|
23
|
+
|
|
24
|
+
## Typography and color
|
|
25
|
+
- Use a predictable type scale with clear role mapping:
|
|
26
|
+
- page title
|
|
27
|
+
- section title
|
|
28
|
+
- body
|
|
29
|
+
- helper/meta text
|
|
30
|
+
- Keep contrast high for critical text and controls.
|
|
31
|
+
- Use semantic color roles (primary/success/warning/danger/info) instead of arbitrary one-off colors.
|
|
32
|
+
|
|
33
|
+
## Component quality checklist
|
|
34
|
+
For every major component, define:
|
|
35
|
+
- default state
|
|
36
|
+
- hover/focus/active/disabled states
|
|
37
|
+
- loading skeleton/spinner behavior
|
|
38
|
+
- empty state
|
|
39
|
+
- error state with recovery action
|
|
40
|
+
- success confirmation when relevant
|
|
41
|
+
|
|
42
|
+
## Forms and validation
|
|
43
|
+
- Put labels outside placeholders (placeholders are hints, not labels).
|
|
44
|
+
- Validate as early as possible without being noisy.
|
|
45
|
+
- Show inline field errors and one global summary for multi-field failures.
|
|
46
|
+
- Disable destructive actions while submitting and show progress feedback.
|
|
47
|
+
|
|
48
|
+
## Responsive behavior
|
|
49
|
+
- Design mobile-first, then scale up.
|
|
50
|
+
- Ensure key actions are reachable with one hand on mobile.
|
|
51
|
+
- Avoid dense tables on small screens; use stacked cards or summary rows.
|
|
52
|
+
|
|
53
|
+
## Accessibility baseline
|
|
54
|
+
- Semantic HTML and proper landmarks.
|
|
55
|
+
- Full keyboard navigation and visible focus states.
|
|
56
|
+
- Click/tap targets large enough for touch.
|
|
57
|
+
- Meaning not conveyed by color alone.
|
|
58
|
+
|
|
59
|
+
## Motion and feedback
|
|
60
|
+
- Use motion to clarify transitions, not as decoration.
|
|
61
|
+
- Keep transitions short and consistent.
|
|
62
|
+
- Avoid blocking animations for critical workflows.
|
|
63
|
+
|
|
64
|
+
## Stack-specific guidance
|
|
65
|
+
- Laravel + TALL: prefer Livewire + Alpine patterns and existing Blade components.
|
|
66
|
+
- Filament admin: prefer Resources and built-in actions before custom UI.
|
|
67
|
+
- Flux UI: use Flux primitives first and keep custom wrappers minimal.
|
|
68
|
+
- Next.js/React: keep component boundaries clean and avoid unnecessary client state.
|
|
69
|
+
|
|
70
|
+
## Handoff quality
|
|
71
|
+
When passing to implementation:
|
|
72
|
+
- provide component mapping to real library components
|
|
73
|
+
- include state matrix (loading/empty/error/success/permission)
|
|
74
|
+
- include responsive and accessibility notes
|
|
75
|
+
- keep the spec concise enough for direct coding
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# Web3 Cardano Patterns
|
|
2
|
+
|
|
3
|
+
> eUTxO model, Aiken validators, and off-chain transaction building. Think in UTxOs, not accounts.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## The eUTxO model — mental model first
|
|
8
|
+
|
|
9
|
+
Cardano uses the **Extended UTxO (eUTxO)** model. Before writing any validator, understand:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
UTxO (Unspent Transaction Output):
|
|
13
|
+
├── Address → who can spend it (public key or script hash)
|
|
14
|
+
├── Value → ADA + any native tokens
|
|
15
|
+
└── Datum → arbitrary data attached to a script UTxO
|
|
16
|
+
|
|
17
|
+
Transaction:
|
|
18
|
+
├── Inputs → UTxOs being consumed (destroyed)
|
|
19
|
+
├── Outputs → new UTxOs being created
|
|
20
|
+
├── Redeemer → data provided to the validator when spending a script UTxO
|
|
21
|
+
└── Validity → time range, signatures, mint/burn
|
|
22
|
+
|
|
23
|
+
Validator:
|
|
24
|
+
→ A Plutus/Aiken script that decides if a UTxO can be spent
|
|
25
|
+
→ Receives: Datum, Redeemer, ScriptContext
|
|
26
|
+
→ Returns: True (allow) or raises error (deny)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Key difference from Ethereum:** State is not stored in the contract — it is stored in UTxOs sitting at the script address. The validator only validates whether a spend is legitimate.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Aiken project structure
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
lib/
|
|
37
|
+
validators/
|
|
38
|
+
marketplace.ak ← spending validator
|
|
39
|
+
minting.ak ← minting policy
|
|
40
|
+
utils/
|
|
41
|
+
math.ak
|
|
42
|
+
value.ak
|
|
43
|
+
types.ak ← shared type definitions
|
|
44
|
+
validators/
|
|
45
|
+
marketplace.ak ← entry points (must be here for compilation)
|
|
46
|
+
minting.ak
|
|
47
|
+
tests/
|
|
48
|
+
marketplace_test.ak
|
|
49
|
+
minting_test.ak
|
|
50
|
+
scripts/
|
|
51
|
+
build.sh
|
|
52
|
+
deploy.sh
|
|
53
|
+
aiken.toml
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Types — define datum and redeemer first
|
|
59
|
+
|
|
60
|
+
```rust
|
|
61
|
+
// lib/types.ak
|
|
62
|
+
pub type AssetClass {
|
|
63
|
+
policy_id: ByteArray,
|
|
64
|
+
asset_name: ByteArray,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
pub type ListingDatum {
|
|
68
|
+
seller: VerificationKeyHash,
|
|
69
|
+
price_lovelace: Int,
|
|
70
|
+
royalty_recipient: VerificationKeyHash,
|
|
71
|
+
royalty_bps: Int, // basis points: 100 = 1%, max 1000
|
|
72
|
+
asset: AssetClass,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
pub type MarketplaceRedeemer {
|
|
76
|
+
Buy
|
|
77
|
+
Cancel
|
|
78
|
+
UpdatePrice { new_price: Int }
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Spending validator
|
|
85
|
+
|
|
86
|
+
```rust
|
|
87
|
+
// validators/marketplace.ak
|
|
88
|
+
use aiken/transaction.{ScriptContext, Spend, Transaction}
|
|
89
|
+
use aiken/transaction/credential.{VerificationKeyHash}
|
|
90
|
+
use aiken/transaction/value
|
|
91
|
+
use lib/types.{ListingDatum, MarketplaceRedeemer}
|
|
92
|
+
|
|
93
|
+
// Constants
|
|
94
|
+
const max_royalty_bps: Int = 1000 // 10%
|
|
95
|
+
|
|
96
|
+
validator {
|
|
97
|
+
fn marketplace(
|
|
98
|
+
datum: ListingDatum,
|
|
99
|
+
redeemer: MarketplaceRedeemer,
|
|
100
|
+
ctx: ScriptContext
|
|
101
|
+
) -> Bool {
|
|
102
|
+
when redeemer is {
|
|
103
|
+
Buy -> {
|
|
104
|
+
let tx = ctx.transaction
|
|
105
|
+
validate_purchase(datum, tx)
|
|
106
|
+
}
|
|
107
|
+
Cancel -> {
|
|
108
|
+
// Only seller can cancel
|
|
109
|
+
must_be_signed_by(ctx.transaction, datum.seller)
|
|
110
|
+
}
|
|
111
|
+
UpdatePrice { new_price } -> {
|
|
112
|
+
and {
|
|
113
|
+
must_be_signed_by(ctx.transaction, datum.seller),
|
|
114
|
+
new_price > 0,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fn validate_purchase(datum: ListingDatum, tx: Transaction) -> Bool {
|
|
122
|
+
// Validate royalty does not exceed maximum
|
|
123
|
+
expect datum.royalty_bps <= max_royalty_bps
|
|
124
|
+
|
|
125
|
+
let royalty_amount = datum.price_lovelace * datum.royalty_bps / 10000
|
|
126
|
+
let seller_amount = datum.price_lovelace - royalty_amount
|
|
127
|
+
|
|
128
|
+
and {
|
|
129
|
+
// Seller receives their share
|
|
130
|
+
output_to_address_has_value(tx, datum.seller, seller_amount),
|
|
131
|
+
// Royalty recipient receives their share (if any)
|
|
132
|
+
if royalty_amount > 0 {
|
|
133
|
+
output_to_address_has_value(tx, datum.royalty_recipient, royalty_amount)
|
|
134
|
+
} else {
|
|
135
|
+
True
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
fn output_to_address_has_value(
|
|
141
|
+
tx: Transaction,
|
|
142
|
+
recipient: VerificationKeyHash,
|
|
143
|
+
min_lovelace: Int
|
|
144
|
+
) -> Bool {
|
|
145
|
+
list.any(tx.outputs, fn(output) {
|
|
146
|
+
when output.address.payment_credential is {
|
|
147
|
+
credential.VerificationKey(vk_hash) ->
|
|
148
|
+
vk_hash == recipient &&
|
|
149
|
+
value.lovelace_of(output.value) >= min_lovelace
|
|
150
|
+
_ -> False
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
fn must_be_signed_by(tx: Transaction, vk_hash: VerificationKeyHash) -> Bool {
|
|
156
|
+
list.has(tx.extra_signatories, vk_hash)
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Minting policy
|
|
163
|
+
|
|
164
|
+
```rust
|
|
165
|
+
// validators/minting.ak
|
|
166
|
+
use aiken/transaction.{ScriptContext, Mint}
|
|
167
|
+
use aiken/transaction/value
|
|
168
|
+
|
|
169
|
+
pub type MintRedeemer {
|
|
170
|
+
Mint { recipient: VerificationKeyHash }
|
|
171
|
+
Burn
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
validator {
|
|
175
|
+
fn nft_policy(redeemer: MintRedeemer, ctx: ScriptContext) -> Bool {
|
|
176
|
+
when ctx.purpose is {
|
|
177
|
+
Mint(policy_id) -> {
|
|
178
|
+
when redeemer is {
|
|
179
|
+
Mint { recipient } -> {
|
|
180
|
+
let tx = ctx.transaction
|
|
181
|
+
// Exactly one token minted with this policy
|
|
182
|
+
let minted = value.tokens(tx.mint, policy_id)
|
|
183
|
+
expect list.length(dict.to_list(minted)) == 1
|
|
184
|
+
|
|
185
|
+
// Transaction signed by recipient
|
|
186
|
+
must_be_signed_by(tx, recipient)
|
|
187
|
+
}
|
|
188
|
+
Burn -> {
|
|
189
|
+
// Validate burning: all minted amounts are negative
|
|
190
|
+
let minted = value.tokens(ctx.transaction.mint, policy_id)
|
|
191
|
+
list.all(dict.values(minted), fn(qty) { qty < 0 })
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
_ -> False
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Tests in Aiken
|
|
204
|
+
|
|
205
|
+
```rust
|
|
206
|
+
// tests/marketplace_test.ak
|
|
207
|
+
use lib/types.{ListingDatum, MarketplaceRedeemer}
|
|
208
|
+
|
|
209
|
+
test royalty_must_not_exceed_maximum() {
|
|
210
|
+
let datum = ListingDatum {
|
|
211
|
+
seller: #"abcd1234",
|
|
212
|
+
price_lovelace: 10_000_000,
|
|
213
|
+
royalty_recipient: #"efgh5678",
|
|
214
|
+
royalty_bps: 1001, // 10.01% — should FAIL
|
|
215
|
+
asset: AssetClass { policy_id: #"", asset_name: #"" },
|
|
216
|
+
}
|
|
217
|
+
// Test that validator rejects this datum
|
|
218
|
+
!validate_purchase(datum, mock_buy_transaction())
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
test seller_receives_correct_amount() {
|
|
222
|
+
let datum = ListingDatum {
|
|
223
|
+
seller: #"abcd1234",
|
|
224
|
+
price_lovelace: 10_000_000, // 10 ADA
|
|
225
|
+
royalty_recipient: #"efgh5678",
|
|
226
|
+
royalty_bps: 300, // 3% = 300_000 lovelace
|
|
227
|
+
asset: AssetClass { policy_id: #"", asset_name: #"" },
|
|
228
|
+
}
|
|
229
|
+
// Seller should receive 9_700_000 lovelace (97%)
|
|
230
|
+
let expected_seller_amount = 9_700_000
|
|
231
|
+
validate_purchase(datum, mock_buy_with_outputs(datum.seller, expected_seller_amount))
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Run tests: `aiken check`
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Off-chain transaction building (Lucid or Mesh)
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
// scripts/list-nft.ts — using Lucid
|
|
243
|
+
import { Lucid, Blockfrost, fromText, Data } from 'lucid-cardano';
|
|
244
|
+
import { ListingDatum } from './types';
|
|
245
|
+
|
|
246
|
+
const lucid = await Lucid.new(
|
|
247
|
+
new Blockfrost('https://cardano-mainnet.blockfrost.io/api/v0', process.env.BLOCKFROST_API_KEY!),
|
|
248
|
+
'Mainnet'
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
lucid.selectWalletFromPrivateKey(process.env.SELLER_KEY!);
|
|
252
|
+
|
|
253
|
+
async function listNFT(
|
|
254
|
+
policyId: string,
|
|
255
|
+
assetName: string,
|
|
256
|
+
priceLovelace: bigint,
|
|
257
|
+
royaltyBps: number
|
|
258
|
+
) {
|
|
259
|
+
const sellerAddress = await lucid.wallet.address();
|
|
260
|
+
const datum: ListingDatum = {
|
|
261
|
+
seller: lucid.utils.getAddressDetails(sellerAddress).paymentCredential!.hash,
|
|
262
|
+
price_lovelace: priceLovelace,
|
|
263
|
+
royalty_recipient: lucid.utils.getAddressDetails(sellerAddress).paymentCredential!.hash,
|
|
264
|
+
royalty_bps: royaltyBps,
|
|
265
|
+
asset: { policy_id: policyId, asset_name: fromText(assetName) },
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const tx = await lucid.newTx()
|
|
269
|
+
.payToContract(
|
|
270
|
+
MARKETPLACE_SCRIPT_ADDRESS,
|
|
271
|
+
{ inline: Data.to(datum, ListingDatum) },
|
|
272
|
+
{ [policyId + fromText(assetName)]: 1n }
|
|
273
|
+
)
|
|
274
|
+
.complete();
|
|
275
|
+
|
|
276
|
+
const signedTx = await tx.sign().complete();
|
|
277
|
+
const txHash = await signedTx.submit();
|
|
278
|
+
console.log(`Listed NFT. Tx hash: ${txHash}`);
|
|
279
|
+
return txHash;
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Datum versioning — plan before deploying
|
|
286
|
+
|
|
287
|
+
```rust
|
|
288
|
+
// Version your datum from day one — cannot change on-chain data after deployment
|
|
289
|
+
pub type DatumV1 {
|
|
290
|
+
seller: VerificationKeyHash,
|
|
291
|
+
price_lovelace: Int,
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Future version — add new fields, keep old validator working
|
|
295
|
+
pub type DatumV2 {
|
|
296
|
+
seller: VerificationKeyHash,
|
|
297
|
+
price_lovelace: Int,
|
|
298
|
+
royalty_bps: Int, // new in V2
|
|
299
|
+
royalty_recipient: Option<VerificationKeyHash>,
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Validator supports both versions via union type
|
|
303
|
+
pub type AnyDatum {
|
|
304
|
+
V1(DatumV1)
|
|
305
|
+
V2(DatumV2)
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Deployment checklist
|
|
312
|
+
|
|
313
|
+
- [ ] Compile: `aiken build` — no warnings
|
|
314
|
+
- [ ] Tests pass: `aiken check`
|
|
315
|
+
- [ ] Script hash documented in `docs/deployment.md`
|
|
316
|
+
- [ ] Datum/redeemer schemas versioned
|
|
317
|
+
- [ ] Off-chain code tested against preview testnet
|
|
318
|
+
- [ ] `min-ada` calculation verified for all output types
|
|
319
|
+
- [ ] Transaction fee estimates verified with realistic UTxO sizes
|
|
320
|
+
- [ ] Emergency cancel mechanism exists for stuck UTxOs
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## ALWAYS
|
|
325
|
+
- Design datum and redeemer types before writing validator logic
|
|
326
|
+
- Store the script hash in deployment notes — cannot recover it otherwise
|
|
327
|
+
- Test with `aiken check` before any on-chain deployment
|
|
328
|
+
- Use `must_be_signed_by` for any privileged action
|
|
329
|
+
- Plan datum versioning from day one
|
|
330
|
+
- Calculate `min-ada` requirements for outputs with tokens/datums
|
|
331
|
+
|
|
332
|
+
## NEVER
|
|
333
|
+
- Deploy a validator you haven't tested with edge-case redeemers
|
|
334
|
+
- Store secrets or private keys in datum (all on-chain data is public)
|
|
335
|
+
- Assume UTxO values — always check with `value.lovelace_of()`
|
|
336
|
+
- Forget that failed validator = UTxO locked forever (no admin override)
|
|
337
|
+
- Use `expect` in production validators without understanding it causes script failure
|