Skip to content
Agentmart
Submit a tool

model-report-form

Build a report screen (turnover sheet, account card, register listing) by extending the shared ReportBase class, which already provides the sticky toolbar…

Skill by IgorAIvanov

IndexedMIT★ 1
Write a review

Chalk Labs

Need help deploying model-report-form?

Chalk Labs sets up agents like this inside real teams: connected to your tools, tested on your data, with a human in the loop where it matters.

Book a call

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/igoraivanov-model-report-form

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Build a report screen (turnover sheet, account card, register listing) by extending the shared ReportBase class, which already provides the sticky toolbar with refresh, print and Excel export.

Use cases: documents, spreadsheets

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/model-report-form

What's inside

Skill name
model-report-form
Description
Build a report screen (turnover sheet, account card, register listing) by extending the shared ReportBase class, which already provides the sticky toolbar with refresh, print and Excel export.
License
MIT
Metadata
audience: app
Instructions
250 lines in SKILL.md · view on GitHub

Files (1)

  • SKILL.md

SKILL.md (first lines)

# Model Report Form Skill

Use this skill when:
- creating a `<Report>Report.ts` screen for a model of type `report`
- adding print or Excel export to a screen that shows a report table
- changing filters or columns of an existing report

**Do not hand-write the toolbar, the print logic or any Excel/CSV generation.**
All of it lives in the shared base class `ReportBase`. Print and export take the
already rendered table, so a report gets both actions with zero configuration —
no column metadata, no manifest block, no server command.

## Base class

`@client/ui-kit/base/report-base.ts` → `ReportBase<Root> extends BaseUI<Root>`
(`@client/` is the alias for the `@altera/client` package).

It owns: the sticky toolbar (**Оновити / Друк / Excel**), the error banner, the
print-only header (report title + subtitle), `window.print()` wiring and the
`.xlsx` build + download.

The subclass declares:

| Member | Required | What it is |
| --- | --- | --- |
| `model` | yes | model name from `manifest.json` |
| `reportTitle` | yes | locale key of the report name (paper + file + sheet name) |
| `buildReport()` | yes | run the query, usually `loadInto("index", …)` |
| `renderBody()` | yes | the `<table>` itself |
| `canRun` | no | guard for required filters (default: not busy) |
| `renderFilters()` | no | pickers and dates under the toolbar |
| `renderToolbarExtra()` | no | extra action buttons in the toolbar |
| `printSubtitle()` | no | organization and period line under the title |
| `hasData` | no | what counts as data (default: `$root.rows` is non-empty) |
| `emptyHint()` | no | what narrowed the result (default: `printSubtitle()`) |

## The empty report is drawn by the base

`renderBody()` is called **only when there is data**. Otherwise the base draws
the empty state, and it tells two cases apart: the report has not been built yet
(«press Refresh») versus it was built and came back empty («no data» plus the
selection that produced it — organization, period).

That difference matters more in a report than in a list: a report's filter is
mandatory, so "we are looking in the wrong place" is the *usual* reason for an
empty screen — a second organization in the database, a period with no documents
— and it is the easiest thing to mistake for a broken report. People go looking
in the SQL.

So **do not write your own «no data» row inside `<tbody>`** — it is unreachable
now. And if an empty row list is still a meaningful answer for your report,
override `hasData` rather than fighting the base: an account card with an opening
balance and no movements in the period has an answer to show, and the same flag
also enables Print and Excel.

A report keeps its own root schema (`<model>.schema.ts` with `$filters`, `rows`,
`totals`) and passes it to `super(...)` — unlike a list, where the base supplies
a generic schema.

## Filters — the same machinery as a list

Report filters are not a separate invention: `$root.$filters`, written through
`setFilter` / `setFilters`, read through `filterValue`, and sent to SQL as a nested
`filters` object. A reference filter is **one key holding `{id, name}`** — never an
`…Id` plus a separate label. The whole contract, with the reasoning and the traps, is in
**[model-list-filters](../model-list-filters/SKILL.md)**; read it before adding a filter
to a report.

**A report whose question is a whole month filters by one date, not a range.** Closing
statements, returns and yearly balances are asked in units, and a range would offer to ask
for a month and a half — a question the report cannot answer. The annotation is the plain
`"x-filter": true`, and the control is `<ui-period units="month">`, which picks the unit
itself instead of taking a date and rounding it behind the user's back. See
[A question asked in whole units](../model-list-filters/SKILL.md).

Two things are specific to reports, and both follow from what a report costs:

- **a filter change does not rebuild the report.** `onFiltersChanged()` stays empty (a
  list overrides it to reload) — a turnover sheet for a year is not something to re-run on
  every click in a picker. `Refresh` builds it, and `canRun` guards the required filters;
- **only the query is hand-written.** `sql:gen` generates the `index` wrapper from the
  filters schema — filter parsing, the required-filter refusal, the `$filters` echo and
  the envelope. What you write is the part a report exists for: the query itself.

**A report that no longer matches its filters says so.** Change a filter after the report
was built and the base blurs the body, disables clicking through it, and floats a
`Parameters changed` notice with a `Rebuild` button over it. Nothing to declare: the base
sets the mark in `onFiltersChanged()` and clears it when the `index` command returns —
one place that every path to building the report goes through. Without it the screen would
show numbers for the old filters under a new period in the panel: everything looks fine,
the number is wrong, and it prints that way too. On paper the blur is gone — printing is a
deliberate act, and a blurred sheet would just be spoilt.

```ts
protected override get canRun(): boolean {
  return !this.busy && !!this.filterValue<Ref>("organization")?.id;
}

protected override async buildReport() {
  await this.loadInto("index", this.filtersPayload());   // → { filters: {…} }
}
```

## SQL: a generated wrapper and a hand-written body

Two functions, and only the second one is yours:

| file | function | who writes it |
|---|---|---|
| `db/_generated/<report>.index.gen.sql` | `app.<report>_index(user_id, payload)` | `deno task sql:gen` |
| `db/<report>.sql` | `app.<report>_data(user_id, filters)` | you |

The wrapper reads the **filters schema** — `<Pascal>FiltersSchema`, the same one the
screen binds to — and does everything around the query:

- parses `payload.filters`, collapsing a reference to its id: `organization` (an object
  `{id, name}`) arrives at your function as `organizationId`, an empty string becomes
  "not set";
- refuses when a required filter is missing, with the message bound to that field —

From IgorAIvanov/altera03, MIT.

More from IgorAIvanov/altera03

Signals

Stars
1
Forks
0
Open issues
0
Last commit (this folder)
Aug 22, 2026 (2 months ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from IgorAIvanov

Done for you

Rather not set it up yourself?

Chalk Labs scopes, builds and runs agents for your team: the task audit, the eval harness, the integrations and the weekly ops report. Fixed scope, priced before we start.

See how we build AI agents →Read the case studies

Chalk Labs is a two-founder AI and growth studio. We build AI agents, CRMs and MVPs for Web3 and AI companies, and have shipped 50+ product and token launches.

launches shipped
50+
launches shipped
media placements
500+
media placements
founders on every account
2
founders on every account

Press we've placed: Forbes · CoinDesk · Decrypt · Cointelegraph · The Block

Rahil Jain, Co-founder, Chalk Labs. Ex-Goldman Sachs engineer. You talk to the person who builds it.

Get model-report-form running in your business

Tell us what you want to automate. A founder replies within one working day with a plan, not a pitch deck.

Goes to the Chalk Labs founders, not the newsletter. Privacy

Need help deploying model-report-form?

CallGet help
Cite this page

model-report-form skill. Agent Mart. https://agentmart.dev/skills/igoraivanov-model-report-form (last updated 2026-10-24)