# How Taskly Works

## How Taskly Works

**Skill level:** Everyone · **Time:** 10 minutes

Read this page once and the rest of the theme stops being a mystery. Taskly is built from a few simple ideas that repeat everywhere.

### The Big Picture
Taskly is a **static site**: when you run `npm run build`, it turns your text files into finished HTML pages in `dist/`. There is no database and no admin panel. Your files *are* the website.

```text
config.toml ──► site settings: name, URL, forms
menu.en.json ─► header and footer links
theme.css ────► colors, radii, type scale

ROUTE  src/pages/[...lang]/pricing.astro
  │    which sections, in which order
  │
  ├─ PAGE  src/content/pricing/english/-index.md
  │        title, SEO, named blocks
  │
  └─ each SECTION
       ├─ default: sections/english/pricing-section.md
       ├─ + page block: pricingSection: { ... }
       └─ look: layout

Base.astro wraps every page:
  head · announcement bar · header · page · CTA · footer · cookies · search
```

### 1. Routes Decide What A Page Shows
Each file in `src/pages/[...lang]/` is one page (a **route**). It lists the sections that page shows, top to bottom. This is the second homepage, `src/pages/[...lang]/home-2.astro`, shortened:

```astro
<Base {...data}>
  <LayoutVariants section="hero" content={data?.heroSection} />
  <LayoutVariants section="logos" content={data?.logosSection} />
  <StatsPanelSection content={data?.statsPanelSection} />
  <FeatureRowsSection content={data?.featureRowsSection} />
  <PricingSection content={data?.pricingSection} />
  <!-- ...more sections... -->
</Base>
```

To **reorder** sections, move a line. To **remove** a section from one page, delete its line. `<LayoutVariants section="hero" …>` renders the hero exactly like `<HeroSection …>` would; it only exists so the theme's own demo can preview other layouts. The `[...lang]` folder name means one file builds every language: English at `/pricing/`, French at `/fr/pricing/`.

### 2. Sections Have Default Content
Every section has a matching content file in `src/content/sections/<language>/`. For example, `PricingSection.astro` reads `pricing-section.md`. That file holds the section's default text, lists, buttons, and options, with comments that explain each one.

Edit a section file and the change appears **everywhere that section is used**. Set `enable: false` at the top of the file to hide the section on every page.

### 3. Pages Can Override Any Section
A page file can change a section **for that page only** with a **named block**. The second homepage uses this to give the shared hero another layout and title:

```yaml
# src/content/homepage/english/home-2.md
---
title: ""
heroSection:          # overrides src/content/sections/english/hero-section.md
  layout: "split"
  title: "Run your *team* from one board"
---
```

Block names are the section name in camelCase: `heroSection`, `pricingSection`, `faqSection`, `ctaSection`, and so on. Each page file lists the blocks it renders in the comments at its top. [Sections](/taskly/customization/sections/) has the full list.

A block with a wrong name, for example `pricing:` instead of `pricingSection:`, is ignored without an error. Copy block names from the page file's comments, the [Sections](/taskly/customization/sections/) table, or `sectionsSchema` in `src/sections.schema.ts`.

<Aside type="caution" title="A block only works where the route uses it">
  Adding `bentoSection:` to the pricing page does nothing, because
  `pricing.astro` does not render that section. Check the route file, or the
  comments at the top of the page file, to see which blocks a page reads.
</Aside>

### 4. How Overrides Merge
When a page block meets the section's default content, Taskly merges them with four rules:

| You write in the page block            | Result                                                         |
| -------------------------------------- | -------------------------------------------------------------- |
| A text or number (`title: "Hello"`)    | Replaces the default value                                     |
| An object (`button:`, `tag:`)          | Merges key by key: only the keys you write change              |
| A list (`plans:`, `items:`, `rows:`)   | Replaces the **whole** list. Write every item you want to keep |
| An empty string (`title: ""`)          | **Clears** the value. Leave the key out to keep the default    |

So this block changes only the button label and keeps its URL, icon, and style:

```yaml
heroSection:
  button:
    label: "Start your trial"
```

### 5. Layouts Change The Look, Not The Content
9 sections can be drawn in more than one way (the page header too). The layout is one field, at the top level of the section's content:

```yaml
layout: "split"   # the hero: default | split | signup
```

The content fields stay the same, so you can switch back and forth freely. Some layouts read a few extra fields (the `split` hero reads `rating`, the `signup` hero reads `pill` and `signup`); the section file groups them under `# layout: <name>` comments. Set the layout in the section file (every page) or in a page block (one page). The **layout gallery** at `/elements/` shows every layout of these sections and of the page header on one page.

See [Section Layouts](/taskly/customization/section-layouts/) for every value.

### 6. Base Wraps Every Page
`src/layouts/Base.astro` adds the parts that every page shares:

- the `<head>`: title, description, Open Graph tags, structured data, fonts, and your `[head]` scripts
- the **announcement bar** above the header, from `src/content/widgets/<language>/announcement-bar.md`
- the floating header, from `config.toml` and the menu files
- the **call to action** card that sits on the top edge of the footer, from `src/content/sections/<language>/call-to-action.md`. A page changes or hides it with a `ctaSection:` block (`enable: false` hides it)
- the footer, from the menu files, `config.toml`, and `src/i18n/<language>.json`, in the layout the page's `footerLayout` picks (`default` when it has none)
- the cookie banner, the search window, the back-to-top button, and shared scripts

The log-in, sign-up, and password screens use `src/layouts/AuthLayout.astro` instead, without the header, footer, or call to action.

### 7. Small Interface Text Lives In i18n Files
Labels that are not page content, such as "Log in", "Subscribe", "Copy", or search messages, live in `src/i18n/en.json` and `src/i18n/fr.json`. Many `config.toml` labels are empty on purpose (`label = ""`): empty means "use the translated text from the i18n file". Fill them in only if your site has one language.

### 8. Config Is Converted Before Astro Reads It
Astro cannot read TOML, so `npm run dev`, `npm run build`, and `npm run astro-check` first convert `src/config/config.toml` into `.astro/config.generated.json`. While `npm run dev` is running, this happens every time you save. Always edit the TOML file, never the generated JSON.

Restart `npm run dev` after changing `trailingSlash` or anything under `[settings.multilingual]`.

### 9. What Happens During A Build
<Steps>

1. `config.toml` is converted to JSON.
2. Astro checks the content collections that have a schema in `src/content.config.ts`, including the named blocks in their page frontmatter (`sectionsSchema` in `src/sections.schema.ts`). A wrong value there stops the build with the file name and field.
3. Every route is rendered once per language into `dist/`. Each section checks its merged content (section file plus page block) against its schema while it renders, so a wrong value in a section file, such as a misspelled `layout`, also stops the build (and shows an error in `npm run dev`).
4. Open Graph images are drawn for pages without an `image`, plus `llms.txt`, `llms-full.txt`, the Markdown copies of pages, the sitemap, `robots.txt`, and the RSS feeds.
5. If the site URL is still the `https://example.com` placeholder, a yellow `[site]` warning prints when the Astro build finishes.
6. Pagefind builds the search index from the finished pages.
7. Drafts and pages with `excludeFromSitemap: true` are removed from the sitemap.

</Steps>

<Aside type="note">
  Unknown keys are not errors. A misspelled key (`descripton:`) or a misnamed
  page block is simply ignored, so if a change does nothing, check the
  spelling against the comments in the file.
</Aside>

### Development vs. Production
Some things behave differently while you work with `npm run dev`:

| Feature                         | `npm run dev`                       | Built site (`npm run build`)          |
| ------------------------------- | ----------------------------------- | ------------------------------------- |
| Pages with `draft: true`        | Shown, so you can preview them      | Not built                             |
| Site search                     | The search window says search works on the built site | Works                 |
| Form endpoint warnings          | Shown inside forms with placeholder URLs, and in the terminal | Printed in the terminal only; visitors never see them |
| Site URL warning                | Not shown                           | Printed after the Astro build step while the URL is `example.com` |
| Open Graph images               | Not drawn                           | Drawn for every page without an `image` |

### Find Where Any Text Comes From
See a sentence on the site and want to change it? Search the whole project for it: in VS Code press <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>F</kbd> (<kbd>⌘</kbd> + <kbd>Shift</kbd> + <kbd>F</kbd> on Mac) and paste a few words. It will be in one of these places, checked in this order:

1. The page's own file, in a named block (`src/content/<page>/<language>/-index.md`)
2. The section's default file (`src/content/sections/<language>/`)
3. A widget file: the dashboard mockup, cookie banner, or announcement bar (`src/content/widgets/<language>/`)
4. The interface text (`src/i18n/<language>.json`)
5. The menus (`src/config/menu.<language>.json`)
6. Site settings (`src/config/config.toml`)

If the same text appears in both a page block and a section file, the page block wins on that page.