CAITSGet started

The example app

The example is a small message board: a home page, and a page where you add, edit and remove messages. It shows each layer and each kind of test. This page follows one message through the app, then lists its files, its design, its tests, and how to remove it.

init my-app --example writes it (see Installation). The app is in the repository IngeniozIT/caits-example, not in the package of CAITS: init gets it with git, at the tag of its version.

What the app does

  • A home page and a page Example (/messages), with the same menu. The menu marks the page that shows. Each page has its title: CAITS, and Example · CAITS.
  • The home page lists the checks of the harness, the first steps, the daily commands, and a prompt for an AI agent. A button copies each command, and the prompt. It then says "Copied", or "Copy failed" when the browser refuses the clipboard.
  • A button of the menu switches between the light theme and the dark theme.
  • On the Example page, the user adds, edits and removes messages. A message has 1 to 280 characters, without the spaces around it. An emoji counts as one character.
  • The backend keeps the messages in the file tmp/messages.json of the project (or in the folder DATA_DIR, when it is set and not empty).
  • If the backend refuses a change (for example an empty message), the page shows why, and keeps what the user typed.
  • With the keyboard, the focus goes where the user acts next after each change. A status tells the screen readers each change that succeeds.
  • Until the user picks a theme, the pages follow the setting of their system: light or dark.

The path of a new message

From the click to the file

MessageBoard.marko                      the view publishes MessageAdditionRequested
  → messagesStore.ts                    the store sends an AddMessageCommand (frontend DTO)
  → add-message.handler.ts (frontend)   the handler calls the MessagesGateway port
  → http-messages-gateway.ts            the adapter posts the text to /api/messages
  → routes/_backend/api/messages        the route gives the request to the controller
  → add-message-http.controller.ts      the controller sends an AddMessageCommand (backend DTO)
  → add-message.handler.ts (backend)    the handler asks the domain, then the MessageRepository port
  → Message.ts                          the domain refuses an empty text, or a text that is too long
  → file-message-repository.ts          the adapter keeps the message in tmp/messages.json

The answer goes back the same way. The frontend handler publishes MessagesChanged or MessagesChangeFailed. The store keeps the result, and after a change it asks for the messages again. The view shows the new list, or the reason for the refusal.

An edit (PUT /api/messages/{id}) and a removal (DELETE /api/messages/{id}) go the same way. How CAITS works draws this path, step by step, on each side.

The files

FolderWhat it shows
src/routes/_frontend/+layout.markoThe menu, which each page shows, the title of each page (titles), and the icon of the tab. It imports the stylesheet of the app.
src/routes/_frontend/style/The stylesheet of the app, app.scss, and the partials that it loads: the values, the theme, the HTML elements, the parts of each page, and the menu.
src/routes/_frontend/component/Logo.markoThe logo: an SVG, with the colors of the theme (Logo.scss). The menu and the home page show it.
src/routes/_frontend/component/ThemeSwitch.markoThe button of the theme, in the menu, with its stylesheet.
src/routes/_frontend/component/CopyButton.markoThe button that copies a command or the prompt of the home page, with its stylesheet.
src/routes/_frontend/+page.marko, messages/+page.markoThe pages: the HTML of the page and the tags of the views, and next to each page its stylesheet (home-page.scss, messages-page.scss). A page has no test.
src/routes/_backend/api/messages/The API routes. A route only gives the request to a controller.
src/backend/messages/domain/An entity that refuses a text that is not valid, its errors, the test of the names of the errors, and a port with its contract.
src/backend/messages/application/Four use cases: add, list, edit and remove. Each has its DTOs, its handler and its test.
src/backend/messages/infra/message-repository/Two adapters of the port: one with a file (production), one in memory (tests). The file adapter runs its changes one after the other: two changes at the same time keep both.
src/backend/messages/presentation/One controller for each use case: from a request to a DTO, and from the output or the error to a response.
src/backend/messages/di.ts, test.di.tsThe bindings of production, and those of the tests.
src/frontend/messages/domain/The gateway port, its contract, and the entity that it gives.
src/frontend/messages/application/The four use cases, and the events of the context, one file each.
src/frontend/messages/infra/messages-gateway/The HTTP adapter (production) and the fake adapter (tests).
src/frontend/messages/presentation/The store, the view with its stylesheet, its presenter and its loader, and the type of the frontend.

The design

The design takes the two colors of the logo, indigo and teal, on a paper of warm stone, or of ink in the dark. This part shows how its styles are organized: copy this shape for your own app.

The styles are in SCSS files, apart from the HTML (see Write the styles). The layout imports style/app.scss, the CSS of all the pages, which loads the partials of style/, in this order:

PartialWhat it holds
The fontsThe packages of the fonts (Fontsource), loaded with @use.
_theme.scssThe colors and the fonts as CSS variables, light and dark, from two mixins.
_elements.scssThe paper, the HTML elements, and the class button.
_page.scssThe parts of each page: its width, the classes eyebrow and lead, and the animation that brings its blocks into place.
_menu.scssThe menu.

_values.scss gives the widths ($page, $narrow, $phone) and the grain of the paper ($grain) to the stylesheets of the routes, and no CSS.

Each page, each component and the view import their own stylesheet, next to them:

StylesheetThe pages that load itWhat it holds
home-page.scssThe home pageThe logo on its glass and the name of CAITS, the cards of the guarantees with the list of the checks, the cards of the first steps, the terminal of the commands, and the prompt for an AI agent.
messages/messages-page.scssThe page ExampleThe width of the page.
component/Logo.scssAll the pages (the menu shows the logo)The two colors of the logo, from the theme: --caits-primary and --caits-accent.
component/ThemeSwitch.scssAll the pagesThe button and its two icons: the moon in the light theme, the sun in the dark theme.
component/CopyButton.scssThe home pageThe small button of the terminals, in their colors.
MessageBoard.scssThe page ExampleThe classes of the view: the form, the cards of the messages, the empty board.

Each class of a page, a component or a view starts with its name: home-page__hero, logo__arch, theme-switch__moon, message-board__composer. The build makes three CSS files: one for all the pages, one for the home page, and one for the page Example.

  • The fonts: Instrument Serif for the titles, Geist for the text, Geist Mono for the code and the labels. init --example adds their packages (Fontsource) to the dependencies. The build puts the files of the fonts in the app: the pages load nothing from another site.
  • The theme: the colors follow the system of the user, until the user clicks the button of the theme. The button puts data-theme="light" or data-theme="dark" on <html>, and keeps the choice in the cookie theme. The layout reads this cookie: the server sends each page in the theme of the user, so a page never shows the other theme first. The layout also gives the theme to the button, and the icon of the button comes from light-dark(), which reads the theme: the button is right before the page runs any script.
  • The motion: the blocks of a page rise into place; on the home page, the logo draws itself, takes its colors, then floats; a new message rises into the list; while a change is on its way, a light runs under the form. The users who ask for less motion (prefers-reduced-motion) see no motion.
  • The busy board: while a change is on its way, the view puts aria-busy="true" on the board. The styles use it, and the screen readers tell it.
  • The focus and the status: Edit gives the focus to the field of the editor. Save and Cancel give it back to the button Edit of the message. After a removal, it goes to the message after, else to the message before, else to the field of the addition. A change that fails gives it back to where the user was. A status, which takes no place on the screen, tells the screen readers "Message added.", "Message changed." or "Message removed.". The buttons Edit and Remove of a message are described by its text.
  • The contrast: each color of text has a contrast of 4.5:1 or more on the paper and on the surface (_theme.scss). A field that has the focus keeps its outline.
  • The icon of the tab: the logo, in the page (an address data:), in the colors of the theme of the system.

The menu marks the page that shows: the layout reads the route in $global.route, and gives aria-current="page" to the link of this route.

The tests

TestFilesWhat they check
Backend domainsrc/backend/messages/domain/entity/Message.test.tsThe names of the errors of the domain, which the tests of the use cases do not see.
Backend application*.test.ts in src/backend/messages/application/The output or the error for a DTO, and what the repository keeps.
Backend presentation*-http.test.tsThe response for a request, and the DTO that the controller sends.
Backend infra*-message-repository.test.tsEach adapter runs the contract of its port, also with changes at the same time.
Backend integrationsrc/backend/context-map.integration.test.tsThe backend with the production bindings: the file in DATA_DIR, or in tmp/.
Frontend application*.test.ts in src/frontend/messages/application/The events that each handler publishes.
Frontend presentationMessageBoard.test.tsThe DTO that each user action sends, the HTML for each state of the store (also aria-busy), and, for a keyboard and a screen reader, the focus after each change and the status.
Frontend infra*-messages-gateway.test.tsEach adapter runs the contract of its port.
End-to-endtest/e2e/layout.spec.tsThe home page: the menu, which marks it, its title, and the switch of the theme, also after a reload and without JavaScript.
End-to-endtest/e2e/home.spec.tsThe buttons of the home page copy the prompt and a command, and tell when the copy fails.
End-to-endtest/e2e/messages.spec.tsThe page Example, from the menu, with its title; a message added, edited and removed in a browser, with the focus and the status; a refused message. Each test starts with no message.

The example passes all the checks of a new project: the architecture rules, 100% coverage and 100% mutation score.

With one backend context, nothing needs a context map yet: the integration test, src/backend/context-map.integration.test.ts, waits next to the place of the map. When a second context reacts to the events of the first, write the map in src/backend/context-map.ts (see Connect two contexts).

Remove the example

When you know the example, remove it, and keep the harness:

  1. Remove the contexts and their routes: src/backend/messages/, src/frontend/messages/, src/routes/_backend/api/messages/ and src/routes/_frontend/messages/.
  2. Remove their tests: src/backend/context-map.integration.test.ts and test/e2e/messages.spec.ts.
  3. In src/routes/_frontend/+layout.marko, remove the Example link of the menu, and the entry "/messages" of titles.
  4. Change the home page, src/routes/_frontend/+page.marko: its step "Try the example" leads to /messages. Its text and its prompt for an AI agent name messages too. When you change what test/e2e/home.spec.ts checks, such as the prompt, change the test with it.
  5. Change the logo, and the fonts, or remove them: the packages @fontsource… in package.json, and their @use in app.scss.
  6. Run npm run check.
Next pageCommands The scripts of a project, the commands of CAITS, and the environment variables.