@tideworthy/tidysites 0.1.20 → 0.1.22
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/README.md +6 -1
- package/bin/tidysites.mjs +20 -3
- package/package.json +2 -1
- package/skill/SKILL.md +132 -0
package/README.md
CHANGED
|
@@ -32,6 +32,7 @@ nothing else; it lives in `.tidysites/key`, which is kept out of git.
|
|
|
32
32
|
| `tidysites gallery [type]` | a screenshot of every section type and layout in this website's theme, or the first screen of one type |
|
|
33
33
|
| `tidysites fetch <url> [--out dir]` | a page of the site being ported: cleaned HTML, its outline (headings, navigation, images, phones, fonts, an Adobe Fonts kit and the families it serves, the stylesheet's own variables and colours) and its stylesheets, inline style blocks included |
|
|
34
34
|
| `tidysites rm <collection>/<slug> ...` | delete entry files with their sidecars (`services/*` for every entry of a collection); `push --prune` then removes them from the website |
|
|
35
|
+
| `tidysites skill [--out <dir>]` | the Working with Tidysites skill (`skill/SKILL.md` in this package, the same document the connector serves as `get_tidysite_skill`) saved into the project as a Claude Code skill, `.claude/skills/tidysites/SKILL.md` unless `--out` says where; run it again after an update |
|
|
35
36
|
| `tidysites publish --yes` | the live site becomes a copy of the preview site. A website that is live on the client's own domain is never published from the command line: the website refuses and says so; the person publishes it (Publish now on the Website tab), or an assistant with them does through the connector after they confirm |
|
|
36
37
|
|
|
37
38
|
A section keeps its type with its key: to replace an intro with a row, give the row its own `_key`
|
|
@@ -63,7 +64,7 @@ shows in `status`.
|
|
|
63
64
|
Styling: a row's `.css` file is scoped to that row (every rule is prefixed with the row's own selector), so it cannot style the rest of the page; rules for the whole page, the sections and the theme's classes go in `business/business.css`.
|
|
64
65
|
|
|
65
66
|
Images: an image field, a hero's image or a Columns image slot takes
|
|
66
|
-
`{ "$media": { "url": "https://...", "alt": "what it shows" } }` (or `{ "$media": { "file": "assets/x.png", "alt": "..." } }` for a picture that exists only as a file in the folder, say an illustration drawn from the old page's inline SVG; the push carries its bytes; a `file` field, such as a row's `media` list or the Business font files, takes a video (mp4, webm, up to
|
|
67
|
+
`{ "$media": { "url": "https://...", "alt": "what it shows" } }` (or `{ "$media": { "file": "assets/x.png", "alt": "..." } }` for a picture that exists only as a file in the folder, say an illustration drawn from the old page's inline SVG; the push carries its bytes; a `file` field, such as a row's `media` list or the Business font files, takes a video (mp4, webm, up to 25 MB, the most one file of a published site may weigh), a font (woff2) or a pdf the same way) and the
|
|
67
68
|
push fetches the file into the website's library; a file the library already
|
|
68
69
|
holds (the same bytes) is reused, not added again. An image already in the
|
|
69
70
|
library keeps the value the pull gave it.
|
|
@@ -94,3 +95,7 @@ names its own in `header_row`.
|
|
|
94
95
|
4. Pages as sections: `pages/<slug>.json`, each a list in `data.sections`. Columns is the section for a band with no record behind it.
|
|
95
96
|
5. The look: brand colours and fonts on the Business record, `business/business.css` for what the tokens cannot say. The theme's own rules are written as `.ts.ts-accent` and `.ts.ts-ink .ts-prose a`, so a rule of yours needs the same weight (`.ts.ts-accent …`) to win.
|
|
96
97
|
6. `tidysites validate`, `tidysites push`, `tidysites shot / --all`, and look at every width: a design often shows a different header above 1440px.
|
|
98
|
+
|
|
99
|
+
## The skill
|
|
100
|
+
|
|
101
|
+
`skill/SKILL.md` is the Working with Tidysites skill: how a site is put together, where every setting lives, the order to port or build, and this command line's part in it. It is the one copy. The connector serves it as `get_tidysite_skill`, the platform app imports it from this package, and `tidysites skill` writes it into a project. A change to a command here, to the content model, or to where a setting lives on the template edits `skill/SKILL.md` in the same pull request, and ships with the next `cli-v*` tag.
|
package/bin/tidysites.mjs
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* fetch <url> [--out dir] a page of the site being ported: cleaned HTML and its outline
|
|
19
19
|
* rm <collection>/<slug> ... delete entry files (globs allowed: services/*); push --prune then removes them from the website
|
|
20
20
|
* publish --yes the live site becomes a copy of the preview site (refused for a site live on the client's own domain: the person publishes that)
|
|
21
|
+
* skill [--out <dir>] save the Working with Tidysites skill into the project (.claude/skills/tidysites/SKILL.md) for an assistant working here
|
|
21
22
|
*
|
|
22
23
|
* The site is the remote: pull, edit, push. A push that would overwrite an
|
|
23
24
|
* entry changed on the website since the pull is refused until you pull
|
|
@@ -399,8 +400,8 @@ async function push(ctx) {
|
|
|
399
400
|
}
|
|
400
401
|
|
|
401
402
|
const MEDIA_TYPES = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp', avif: 'image/avif', svg: 'image/svg+xml', ico: 'image/x-icon', mp4: 'video/mp4', m4v: 'video/mp4', webm: 'video/webm', mov: 'video/quicktime', woff2: 'font/woff2', woff: 'font/woff', ttf: 'font/ttf', otf: 'font/otf', pdf: 'application/pdf' };
|
|
402
|
-
/** Pictures travel up to 15 MB; a video
|
|
403
|
-
const mediaCap = (type) => (type.startsWith('video/') ?
|
|
403
|
+
/** Pictures travel up to 15 MB; a video up to 25 MB, the most one file of a published site may weigh. */
|
|
404
|
+
const mediaCap = (type) => (type.startsWith('video/') ? 25 : 15);
|
|
404
405
|
/** Every { "$media": { "file": ... } } in the document becomes { "$media": { "data", "type", "filename", "alt" } }, read from the folder. */
|
|
405
406
|
function inlineMediaFiles(doc, dir) {
|
|
406
407
|
const walk = (v) => {
|
|
@@ -414,7 +415,7 @@ function inlineMediaFiles(doc, dir) {
|
|
|
414
415
|
const bytes = readFileSync(path);
|
|
415
416
|
const ext = m.file.split('.').pop().toLowerCase();
|
|
416
417
|
const type = MEDIA_TYPES[ext] ?? 'application/octet-stream';
|
|
417
|
-
if (bytes.length > mediaCap(type) * 1024 * 1024) fail(`$media.file ${m.file}: ${(bytes.length / 1048576).toFixed(1)} MB; ${type.startsWith('video/') ? 'videos up to
|
|
418
|
+
if (bytes.length > mediaCap(type) * 1024 * 1024) fail(`$media.file ${m.file}: ${(bytes.length / 1048576).toFixed(1)} MB; ${type.startsWith('video/') ? 'videos up to 25 MB (the most one file of a published site may weigh; re-encode it smaller)' : 'pictures up to 15 MB'}`);
|
|
418
419
|
return { $media: { data: bytes.toString('base64'), type, filename: m.file.split('/').pop(), alt: typeof m.alt === 'string' ? m.alt : '' } };
|
|
419
420
|
}
|
|
420
421
|
return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, walk(x)]));
|
|
@@ -689,6 +690,20 @@ async function publish(ctx) {
|
|
|
689
690
|
say(json ? JSON.stringify(json, null, 2) : text);
|
|
690
691
|
}
|
|
691
692
|
|
|
693
|
+
/**
|
|
694
|
+
* The Working with Tidysites skill, the same document the connector serves as get_tidysite_skill, written into
|
|
695
|
+
* the project so an assistant working in this folder has it without the connector. It ships with this package,
|
|
696
|
+
* so its version is this package's; run it again after an update to refresh the copy.
|
|
697
|
+
*/
|
|
698
|
+
function skillCmd() {
|
|
699
|
+
const src = new URL('../skill/SKILL.md', import.meta.url);
|
|
700
|
+
const outDir = flags.out ? resolve(String(flags.out)) : join(process.cwd(), '.claude', 'skills', 'tidysites');
|
|
701
|
+
mkdirSync(outDir, { recursive: true });
|
|
702
|
+
const target = join(outDir, 'SKILL.md');
|
|
703
|
+
writeFileSync(target, readFileSync(src, 'utf8'));
|
|
704
|
+
say(`Saved ${relative(process.cwd(), target) || target}. An assistant that reads skills from .claude/skills has it from here; the connector's get_tidysite_skill says the same thing.`);
|
|
705
|
+
}
|
|
706
|
+
|
|
692
707
|
function help() {
|
|
693
708
|
say(readFileSync(new URL(import.meta.url), 'utf8').split('\n').slice(2, 20).map((l) => l.replace(/^ \* ?/, '')).join('\n'));
|
|
694
709
|
say(`\nFiles:\n <collection>/<slug>.json { "id", "slug", "status", "updated_at", "data": {...} }; a new entry is a new file with "data"\n <collection>/<slug>.css data.custom_css as a file (rows: data.css); rows/<slug>.html is data.html\n menus/<name>.json a menu as a tree: { label, items: [{ label, url, children: [...] }] }\n ${META}/ config, key, the last pull, the content model, screenshots\n\nA field taken out of a file is cleared on the website at the next push; a file you changed here is never overwritten by a pull.\nImages: an image field or a Columns image slot takes { "$media": { "url": "https://...", "alt": "..." } } (the push fetches it into the library) or { "$media": { "file": "assets/x.png", "alt": "..." } } for a picture that exists only as a file in this folder (the push carries its bytes).\nReferences: a record names another by its id, or "$ref:<slug-or-id>" for one in the same push.`);
|
|
@@ -761,6 +776,8 @@ async function main() {
|
|
|
761
776
|
return rmCmd(config());
|
|
762
777
|
case 'publish':
|
|
763
778
|
return publish(config());
|
|
779
|
+
case 'skill':
|
|
780
|
+
return skillCmd();
|
|
764
781
|
case 'help':
|
|
765
782
|
case '--help':
|
|
766
783
|
case '-h':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tideworthy/tidysites",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "Pull a Tidysites website to files, edit, validate against its content model, push it back, screenshot the result.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
11
|
"lib",
|
|
12
|
+
"skill",
|
|
12
13
|
"README.md"
|
|
13
14
|
],
|
|
14
15
|
"engines": {
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tidysites
|
|
3
|
+
description: Working with Tidysites, the hosted websites: how a site is built through its content and settings (never its code), where every setting lives, the command line for a whole site at once, the order to port an existing site or build a custom one, and what to do when the content path cannot do something. Use whenever someone builds, ports, changes or asks about a Tidysites website.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Working with Tidysites
|
|
7
|
+
|
|
8
|
+
This document is the same one the Tidysites connector serves as `get_tidysite_skill`. When it is installed in the project (this file), read it here and skip that tool: they say the same thing. It ships with the `@tideworthy/tidysites` command line and `npx tidysites skill` writes it into a project; its version is the command line's.
|
|
9
|
+
|
|
10
|
+
A Tidysite is a template (the data shape every site shares), a theme (the look), and the site's own content and settings. Everything a site needs is set through its content and settings, by a person in the site's admin or by you through these tools. There is no code door: what the content path cannot do is reported with `report_website_limit` so the template grows for every site.
|
|
11
|
+
|
|
12
|
+
Content work happens on the preview site. The live site changes only when `publish_website` runs or a person presses Publish now.
|
|
13
|
+
|
|
14
|
+
## The three layers
|
|
15
|
+
|
|
16
|
+
1. **Records.** The things with a page of their own or reused across pages: the Business record, Locations, Services and Service categories, Service areas, Audiences (who a service is for), People, Posts, Products, Case studies, Pages, Forms, Custom rows, Saved sections, and the FAQs and Reviews when they are shared. Logos, insurers, financing providers, technologies, conditions, policies, downloads, offers and badges are written in their section as items; a collection of them exists only for the shared case. Read and write them with `list_website_content`, `get_website_entry`, `create_website_entry`, `update_website_entry`, `edit_website_section` and `replace_website_text`.
|
|
17
|
+
2. **Sections.** A page is a list of sections. Each section type has layouts and fields; `get_website_schema` lists them all. Reorder, add, remove and fill sections as content.
|
|
18
|
+
3. **The look.** Brand colours, fonts, corner style, spacing, type size and width on the Business record; the theme fills in whatever the record leaves empty; the Business record's custom CSS has the last word. Every theme uses the same class names (`ts-header`, `ts-nav-*`, `ts-footer-*`, `ts-card`, `ts-form`, ...), so CSS written on one theme carries to the next. Every section carries `ts-section`, `ts-type-<type>` and `ts-key-<key>`, and the body carries `ts-<collection>` and `ts-page-<slug>`, so a rule can address one section on one page.
|
|
19
|
+
|
|
20
|
+
## Where things live
|
|
21
|
+
|
|
22
|
+
| You want | Where it is |
|
|
23
|
+
|---|---|
|
|
24
|
+
| The company's facts, brand colours, fonts, booking and portal buttons | Business record |
|
|
25
|
+
| Hours day by day, parking, map | Locations records; the Business details section places them |
|
|
26
|
+
| Menu with dropdowns or a mega panel | The primary menu's items and their children (`menu_get`, `menu_set_items` through `call_website_site_tool`) |
|
|
27
|
+
| A top bar line, the mobile call bar, a pop-up (offer or form, on arrival, after a wait, on scroll, on leaving), replacing the header or footer with a custom row | Frame record |
|
|
28
|
+
| Live chat, reviews widget, booking embed, Instagram feed, accessibility widget, pixels (Meta, Google Ads, TikTok, LinkedIn), call tracking (CallRail, WhatConverts), heatmaps (Hotjar, Clarity), Adobe Fonts kit | Integrations record: pick the provider, paste the vendor's embed or the id |
|
|
29
|
+
| Google Tag Manager or GA4 | Business record: gtm_id or ga4_id |
|
|
30
|
+
| Cookie consent bar | Frame record: cookie_banner on, text, policy link, button labels; trackers wait for Accept, chat and call tracking do not |
|
|
31
|
+
| Uploaded brand fonts (woff2) | Frame record: display_font_file, body_font_file; they take over the font tokens |
|
|
32
|
+
| Footer column headings, services column on or off | Frame record: footer_services_label, footer_services, footer_menu_label |
|
|
33
|
+
| Blog categories, tags, authors, posts per page | Posts: category, tags (comma-separated), author (a person); Frame record: blog_page_size; the site renders /blog/category/..., /blog/tag/..., /blog/author/... and /blog/page/N |
|
|
34
|
+
| A page in another language | Page: language (code) and translation_of (the title of the page it translates); Frame record: site_language; hreflang links follow |
|
|
35
|
+
| Where a record's page lives (/locations/{slug} instead of /service-areas/{slug}, a location at the root) | The collection's URL pattern: `schema_update_collection` through `call_website_site_tool` with `{ urlPattern }`; it is the site's own setting from then on |
|
|
36
|
+
| A service, area or product that is data only, with no page of its own | The record's `page` switch off (people: has_profile, technologies: has_page); the sections still list it, with no link |
|
|
37
|
+
| A listing page (/services, /team, ...) on or off | Frame record: services_index, areas_index, team_index, audiences_index, conditions_index, products_index, technologies_index, case_studies_index; a page record at the same slug takes the address |
|
|
38
|
+
| The title tag, meta description, canonical and whether a page is in search engines | The record's own fields, empty by default: meta_title, meta_description, canonical, noindex; empty means derived from the record (title, summary, its own address) |
|
|
39
|
+
| The share title, description and picture (link previews) | social_title, social_description, share_image on the record; empty means the meta title, the description, and the record's own picture, then the page's hero, then the logo |
|
|
40
|
+
| Structured data | Generated on every page: the business (its type from the Business record's schema_type, address unless hide_address, logo, socials, price_range, founding year, map, hours from the primary location, one entry per other location), the breadcrumb trail, the record's entry (Service, Person, BlogPosting, Product) and FAQPage from the page's FAQ sections; the record's `schema` (JSON-LD) replaces the page's own entries, the frame record's site_schema replaces the business entry |
|
|
41
|
+
| A page under another page (/service-area/livonia) | The page's `parent` field: the parent page's slug; the address is the parent's plus its own, the breadcrumb follows; a page with a parent answers only there |
|
|
42
|
+
| Links, lists and bold inside a section's copy | The section's `copy` field (rich text, markdown through the connector), shown instead of its plain Text when set |
|
|
43
|
+
| A form's questions, including a file upload | Forms record: `questions` is a list of { label, kind, options, required, placeholder }; kinds: short answer, long answer, one choice, several choices, date, number, file upload (a document or photo up to 10 MB, kept with the lead and shown on its page) |
|
|
44
|
+
| A second Tag Manager container | Integrations record: gtm_id_2 |
|
|
45
|
+
| CallTrackingMetrics number swapping | Integrations record: call_tracking = CallTrackingMetrics, call_tracking_id = the account id or the pasted swap script |
|
|
46
|
+
| Every address the site renders, with preview links | `list_website_pages` |
|
|
47
|
+
| What a page looks like, on a laptop and a phone; what another site looks like | `screenshot_website` (a path on the preview or live site, or any URL); the images come back and each has a permanent URL |
|
|
48
|
+
| The rendered HTML of a page, section by section, with the keys to edit | `get_website_page`; then `edit_website_section` or `replace_website_text` |
|
|
49
|
+
| Redirects | `list_website_redirects`, `add_website_redirect`, `remove_website_redirect`; the site writes its own when a published slug changes; they answer on the preview site now and on the live site after the next publish |
|
|
50
|
+
| A fixed label the theme prints ("Call or text", "Hours", "Read them all", a form's "Name"), in the site's own words | Frame record: wording, one line per change as Original => Replacement; `search_website_text` lists these labels as theme_labels and `replace_website_text_hits` writes the line |
|
|
51
|
+
| Where a phrase or a pattern appears across the whole site (a phone number, a name, bracketed leftovers from a paste, placeholder text, double spaces, web addresses) | `search_website_text`; it reads only and names each entry, field and section with snippets |
|
|
52
|
+
| Change a phrase in the places a search found | `replace_website_text_hits` with the hits to change; every occurrence in that field or section is replaced, one write per entry |
|
|
53
|
+
| A link on a phrase inside a page's written content, to another page of the site | `add_website_link`; refused when the phrase is missing, inside a link, across formatting, already linked to that target, or the entry's own page; when the phrase repeats, the sentence that holds it (context) is required |
|
|
54
|
+
| Which addresses visitors are asking for and not finding right now | `list_website_404s` (the preview site's own log, grouped by path); `check_website_old_urls` for the port |
|
|
55
|
+
| Alt text on an image already in the library; the images that have none | `set_website_image_alt`; `list_website_media` with missing_alt |
|
|
56
|
+
| Which old addresses still 404 after a port | `check_website_old_urls` (the last crawl of the old site, each address tried on the new one; it writes nothing) |
|
|
57
|
+
| A form with its own questions, a newsletter signup | Forms records; name one from a section's Form field, the header's booking button, or the pop-up |
|
|
58
|
+
| A button that opens a form in a pop-up on the same page (register, sign up, request a quote) | Give the button a link of `#form:<Form name>`: the hero's buttons, the intro's, the Cards button, the call to action band's, or a Columns slot's link; the form opens in a dialog and nothing leaves the page |
|
|
59
|
+
| A row of cards, steps, stats, logos, insurers, financing providers, technologies, conditions, policies, downloads | The section carries its own items (Cards, Steps, Numbers, Badges, Brands, Clients, Insurance, Payment and financing, Technology, Conditions, Policies, Forms); the matching collection is read only when the section has no items |
|
|
60
|
+
| A section used on several pages, edited once | A Saved section: a record in Saved sections (its name, the section) placed with the Saved section type by name; on the staging site the chip on any section offers Share, and on a shared one Detach |
|
|
61
|
+
| What every section type and layout looks like in this theme | `screenshot_website` on `/_tidy/gallery` on the preview site; `#<type>` jumps to one (`/_tidy/gallery#columns`) |
|
|
62
|
+
| A person changing one heading, paragraph, image or a row's text on the preview site | In edit mode a click on it edits it where it stands; the chip's Edit opens the whole section |
|
|
63
|
+
| Two or more columns, a band of text with a background, a logo strip, cards two across, an intro above a grid: content that lives on this page only | The Columns section: any number of slots, `across` per row on a laptop; each slot is text (markdown: headings, lists, links, bold), an image, a card (icon, heading, text, link), a number, a form by name, a video by URL, or your own HTML; a slot with span "full row" is the intro or outro; on the section a tone (light, dark, accent) or a background photo, width, alignment, a CSS class, and "continue the background of the section above" |
|
|
64
|
+
| Reviews, team, services, logos, gallery as a carousel; FAQ as tabs; plans or products as a comparison table | A layout on the section |
|
|
65
|
+
| A page with no menu, or its own header | The page's Header and Footer fields: the site's, none, or a custom row |
|
|
66
|
+
| Markup no section offers | A Custom row: your HTML and CSS, rendered in the page, nothing runs in it |
|
|
67
|
+
| A calculator or an embed that needs script | The Embed (sandboxed) section |
|
|
68
|
+
| Photos | `add_website_image` from a URL, then the image field on the record or section |
|
|
69
|
+
|
|
70
|
+
## A whole site at once: the command line
|
|
71
|
+
|
|
72
|
+
The tools above change one thing at a time, which is right for a live site: a heading, a form, a redirect, a word everywhere. Building a site or porting one is a whole-site job (every record, every page, the images, the CSS), and for that the website is a folder of files: pull it, write the files, check them against the content model, push them back in one write, look at the result.
|
|
73
|
+
|
|
74
|
+
`connect_tidysites_cli` gives the command that sets it up (it carries a credential for this person scoped to the workspace's websites, and nothing else); the website's page in the app has the same command under AI Connector. Hand it to the person, or run it yourself when you have a shell:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
npx -y @tideworthy/tidysites@latest init <site_id> --host <the app's address> --key <key>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Then, in that folder, `npx tidysites help` lists every command with its flags. The ones a build or a port leans on:
|
|
81
|
+
|
|
82
|
+
| | |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `npx tidysites pull` | every record and page as files: `<collection>/<slug>.json` with `{ id, slug, status, updated_at, data }`, `business/business.css` for the custom CSS, `rows/<slug>.html` and `.css` for a custom row, `menus/primary.json` and `menus/footer.json` as trees (an item with `children` is a dropdown) |
|
|
85
|
+
| `npx tidysites schema` / `schema columns` | the section types, then one type's fields and options |
|
|
86
|
+
| `npx tidysites gallery columns` | one section type with its layouts rendered in this website's theme (the whole gallery without a type) |
|
|
87
|
+
| `npx tidysites fetch <url>` | a page of the site being ported: cleaned HTML, its outline (headings, navigation, images, phones, fonts, colours) and its stylesheets |
|
|
88
|
+
| `npx tidysites validate` | every file against the content model, with the file and the field in each message |
|
|
89
|
+
| `npx tidysites push` | the created and updated entries back in one write (`--prune` deletes the entries whose files are gone; `--dry` shows what would go); refused when an entry changed on the website since the pull |
|
|
90
|
+
| `npx tidysites rm services/old-one` | delete entry files with their sidecars (`services/*` for a whole collection); `push --prune` then removes them from the website |
|
|
91
|
+
| `npx tidysites shot /` and `shot /services --mobile` | the result, as pictures in `.tidysites/shots/`; `--menu` opens every menu first |
|
|
92
|
+
| `npx tidysites measure /` | the page as the browser lays it out: every heading, paragraph, button, picture and section with its computed type and box, to style to the old page's numbers |
|
|
93
|
+
| `npx tidysites speed /` | PageSpeed Insights for a page of the preview or live site; `--against <old url>` tests the old page beside it |
|
|
94
|
+
| `npx tidysites pages` / `page /services` | every address the website renders; one rendered page section by section with the keys to edit |
|
|
95
|
+
| `npx tidysites redirect add /old /new` | an old address that must keep working after a port |
|
|
96
|
+
| `npx tidysites publish --yes` | the live site becomes a copy of the preview site; refused for a website live on the client's own domain, which the person publishes from its page |
|
|
97
|
+
| `npx tidysites skill` | this document, saved into the project as a Claude Code skill (`.claude/skills/tidysites/SKILL.md`) |
|
|
98
|
+
|
|
99
|
+
A new entry is a new `<collection>/<slug>.json` with `"data"`; the slug is the file name. A field taken out of a file is cleared on the website at the next push; a pull never overwrites a file changed and not yet pushed. An image is `{ "$media": { "url": "https://...", "alt": "..." } }` in any image field or Columns image slot and the push fetches it. A record names another by its id, or `"$ref:<slug>"` for one created in the same push. Rich text is markdown. A row's `.css` is scoped to that row; rules for the whole page go in `business/business.css`.
|
|
100
|
+
|
|
101
|
+
Without a shell, `pull_website` and `push_website` do the same from here: the whole document in, the whole document out.
|
|
102
|
+
|
|
103
|
+
## Porting an existing site
|
|
104
|
+
|
|
105
|
+
In this order, through the command line above (one page at a time through the tools is the slow way):
|
|
106
|
+
|
|
107
|
+
1. **Records first.** Business facts and brand from the old site, the locations, the services, the people, the reviews, the FAQs. Photos through `add_website_image`.
|
|
108
|
+
2. **The frame.** Menu tree with children; booking and portal buttons; phone in the header or not; top bar and call bar; third parties by id.
|
|
109
|
+
3. **Pages as sections.** Rebuild each page from the section types and their layouts. Reach for Columns when a band has no record behind it: text beside an image, cards in a grid, a logo strip, a title band; its slots take markdown, so in-copy links, lists and bold carry over. The list sections (Services, Team, Reviews, Service areas) stay the home of anything that is a record.
|
|
110
|
+
4. **The look.** Colours, fonts, corner style, spacing on the Business record; then custom CSS against the class names for what the tokens cannot say.
|
|
111
|
+
5. **What the theme already prints.** A service's, an area's or a person's page opens with the theme's own header built from the record (headline, summary, the call button); do not add a hero section on top of it, or the page gets two headlines. The theme's rules carry the weight of two classes (`.ts.ts-accent`, `.ts.ts-ink .ts-prose a`), so custom CSS that should win is written the same way.
|
|
112
|
+
6. **Custom rows last.** Only for markup no section, no layout and no Columns slot can give: a header laid out like the old one, an angled shape, a drawn map. Write the data in by hand; keep rows few, because they do not read records.
|
|
113
|
+
7. **Look, then compare.** `screenshot_website` on the new page and on the old one; `get_website_page` to find the section behind what the picture shows.
|
|
114
|
+
8. **Old addresses.** `check_website_old_urls` lists the old site's pages that answer 404 on the new one. Decide each: `add_website_redirect` to the page that replaces it, a 410 when it is gone for good, or leave it. The check writes nothing and never decides for you.
|
|
115
|
+
9. **SEO.** Meta titles and descriptions where the old site had deliberate ones (the derived ones stand otherwise), `noindex` on thank-you and paid-traffic pages, the Business record's schema_type and price_range. The structured data follows from the records.
|
|
116
|
+
10. **Publish** when the preview site is right. The live site is a copy of the preview site; redirects go with it.
|
|
117
|
+
|
|
118
|
+
## A custom design
|
|
119
|
+
|
|
120
|
+
The same order, with the look coming from the client's wishes instead of an old site. When the client points at another site for a header or a blog they like, `screenshot_website` with that URL puts it beside your page. Match with tokens and CSS before markup, and markup before anything else.
|
|
121
|
+
|
|
122
|
+
## When you cannot do it
|
|
123
|
+
|
|
124
|
+
Say so with `report_website_limit`: what was wanted, in one line, and what was tried. It reaches the team; the person asking needs no other step. Do not work around a gap with an embed that fakes it.
|
|
125
|
+
|
|
126
|
+
## Rules of thumb
|
|
127
|
+
|
|
128
|
+
- A thing gets a record when it is reused across pages or has a page of its own; it gets a repeater inside its section when it lives only in that spot.
|
|
129
|
+
- A provider is an id, never pasted code: if the vendor is not on the list, report it.
|
|
130
|
+
- Keep `_key` on every section you send back; a block without one is new.
|
|
131
|
+
- Check at a phone width and a laptop width before calling a page done.
|
|
132
|
+
- Nothing goes live until `publish_website`; say so when a person asks why the live site has not changed.
|