Add dockerized Playwright E2E (todo §1); screenshot live pages + foundation mockups, assert shared design-system styles match
This commit is contained in:
@@ -4,3 +4,5 @@ npm-debug.log
|
|||||||
*.log
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
html-css-foundation
|
html-css-foundation
|
||||||
|
|
||||||
|
e2e/artifacts
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,3 +2,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
*.log
|
*.log
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
|
# Playwright E2E outputs (screenshots, html report, traces)
|
||||||
|
e2e/artifacts/
|
||||||
|
|||||||
12
Dockerfile.e2e
Normal file
12
Dockerfile.e2e
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Playwright runner — browsers preinstalled, pinned to match @playwright/test in e2e/.
|
||||||
|
# Built/run via compose.e2e.yml; targets the `web` service over the network.
|
||||||
|
FROM mcr.microsoft.com/playwright:v1.49.1-noble
|
||||||
|
|
||||||
|
WORKDIR /e2e
|
||||||
|
|
||||||
|
COPY e2e/package.json e2e/package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
COPY e2e/ ./
|
||||||
|
|
||||||
|
CMD ["npx", "playwright", "test"]
|
||||||
17
README.md
17
README.md
@@ -137,9 +137,23 @@ auto-merged by `docker compose up`) turns them back off for live editing.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose run --rm web npm run typecheck # strict tsc --noEmit
|
docker compose run --rm web npm run typecheck # strict tsc --noEmit
|
||||||
docker compose run --rm web npm test # node --test
|
docker compose run --rm web npm test # node --test (units)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### End-to-end (Playwright)
|
||||||
|
|
||||||
|
E2E runs in the official Playwright image (browsers preinstalled) against the live `web`
|
||||||
|
service — no Node/browsers on the host. It screenshots the live pages **and** the
|
||||||
|
`html-css-foundation` mockups, then asserts the live DOM computes the **same design-system
|
||||||
|
styles** as the reference (so a styling regression fails the build, independent of the row data).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f compose.yml -f compose.e2e.yml run --rm e2e # run the suite
|
||||||
|
docker compose -f compose.yml -f compose.e2e.yml down -v # tear down after
|
||||||
|
```
|
||||||
|
|
||||||
|
Screenshots + an HTML report land in `e2e/artifacts/` (git-ignored). Tests run in parallel.
|
||||||
|
|
||||||
## Building a plugin _(planned)_
|
## Building a plugin _(planned)_
|
||||||
|
|
||||||
A plugin is a folder under `plugins/`. The host discovers it at boot — no
|
A plugin is a folder under `plugins/`. The host discovers it at boot — no
|
||||||
@@ -370,6 +384,7 @@ views/ Core EJS templates (index = the app-shell People dashboard,
|
|||||||
public/ Static assets under /public/ (css/styles.css + auth.css, favicon, robots.txt)
|
public/ Static assets under /public/ (css/styles.css + auth.css, favicon, robots.txt)
|
||||||
config/menu.ts Central menu override + branding (planned)
|
config/menu.ts Central menu override + branding (planned)
|
||||||
plugins/ Drop-in plugin folders, auto-discovered (planned)
|
plugins/ Drop-in plugin folders, auto-discovered (planned)
|
||||||
|
e2e/ Playwright visual + functional E2E (Dockerfile.e2e + compose.e2e.yml run it)
|
||||||
html-css-foundation/ HTML design mockups — the source for the building-block
|
html-css-foundation/ HTML design mockups — the source for the building-block
|
||||||
partials; reference the stylesheets in public/css/.
|
partials; reference the stylesheets in public/css/.
|
||||||
```
|
```
|
||||||
|
|||||||
31
compose.e2e.yml
Normal file
31
compose.e2e.yml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Playwright E2E. Brings up the app + a Playwright runner, screenshots the live pages and the
|
||||||
|
# html-css-foundation mockups, and asserts the live DOM computes the same design styles.
|
||||||
|
# docker compose -f compose.yml -f compose.e2e.yml run --rm e2e
|
||||||
|
# docker compose -f compose.yml -f compose.e2e.yml down -v # tear down after
|
||||||
|
# Screenshots + HTML report land in ./e2e/artifacts/ (git-ignored).
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
# Dev throwaways are fine for tests; cache templates for production-like rendering.
|
||||||
|
environment:
|
||||||
|
CACHE_TEMPLATES: "true"
|
||||||
|
REQUIRE_SECURE_SECRETS: "false"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:3000/"]
|
||||||
|
interval: 2s
|
||||||
|
timeout: 4s
|
||||||
|
retries: 15
|
||||||
|
|
||||||
|
e2e:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.e2e
|
||||||
|
depends_on:
|
||||||
|
web:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
BASE_URL: http://web:3000
|
||||||
|
volumes:
|
||||||
|
# The mockups + their stylesheet, kept as siblings so file:// ../public/css resolves.
|
||||||
|
- ./html-css-foundation:/repo/html-css-foundation:ro
|
||||||
|
- ./public:/repo/public:ro
|
||||||
|
- ./e2e/artifacts:/e2e/artifacts
|
||||||
78
e2e/package-lock.json
generated
Normal file
78
e2e/package-lock.json
generated
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"name": "plainpages-e2e",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "plainpages-e2e",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "1.49.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@playwright/test": {
|
||||||
|
"version": "1.49.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz",
|
||||||
|
"integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright": "1.49.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright": {
|
||||||
|
"version": "1.49.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz",
|
||||||
|
"integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright-core": "1.49.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright-core": {
|
||||||
|
"version": "1.49.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz",
|
||||||
|
"integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"playwright-core": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
e2e/package.json
Normal file
13
e2e/package.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "plainpages-e2e",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Playwright visual + functional E2E: live app vs the html-css-foundation design.",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"test": "playwright test"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "1.49.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
20
e2e/playwright.config.ts
Normal file
20
e2e/playwright.config.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
|
// Visual + functional checks against the live app (the `web` compose service, BASE_URL) and the
|
||||||
|
// static html-css-foundation mockups (bind-mounted at /repo). Run via compose.e2e.yml. Parallel
|
||||||
|
// per the project's E2E principle (todo §1.1); deterministic colorScheme/viewport so the
|
||||||
|
// computed-style parity vs the reference design is stable.
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: ".",
|
||||||
|
outputDir: "artifacts/test-output",
|
||||||
|
fullyParallel: true,
|
||||||
|
forbidOnly: true,
|
||||||
|
reporter: [["list"], ["html", { open: "never", outputFolder: "artifacts/report" }]],
|
||||||
|
use: {
|
||||||
|
baseURL: process.env.BASE_URL ?? "http://localhost:3000",
|
||||||
|
colorScheme: "light",
|
||||||
|
screenshot: "only-on-failure",
|
||||||
|
viewport: { width: 1280, height: 800 },
|
||||||
|
},
|
||||||
|
projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }],
|
||||||
|
});
|
||||||
103
e2e/visual.spec.ts
Normal file
103
e2e/visual.spec.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { mkdir } from "node:fs/promises";
|
||||||
|
import { expect, test, type Page } from "@playwright/test";
|
||||||
|
|
||||||
|
// The mockups are bind-mounted at /repo (sibling to /repo/public so their ../public/css/ resolves).
|
||||||
|
const MOCKUP = "file:///repo/html-css-foundation";
|
||||||
|
const APP_SHELL = `${MOCKUP}/App%20Shell.html`;
|
||||||
|
const AUTH = `${MOCKUP}/Auth.html`;
|
||||||
|
const SHOTS = "artifacts/screenshots";
|
||||||
|
|
||||||
|
const shot = (page: Page, name: string): Promise<Buffer> =>
|
||||||
|
page.screenshot({ fullPage: true, path: `${SHOTS}/${name}.png` });
|
||||||
|
|
||||||
|
test.beforeAll(async () => { await mkdir(SHOTS, { recursive: true }); });
|
||||||
|
|
||||||
|
test("captures live pages + reference mockups for side-by-side review", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await expect(page.locator(".sidebar")).toBeVisible();
|
||||||
|
await expect(page.locator("table.table tbody tr").first()).toBeVisible();
|
||||||
|
await shot(page, "live-01-dashboard");
|
||||||
|
|
||||||
|
await page.goto("/?sort=-name&status=active");
|
||||||
|
await shot(page, "live-02-sorted-filtered");
|
||||||
|
|
||||||
|
await page.goto("/");
|
||||||
|
await page.locator("#theme-dark").check({ force: true }); // visually-hidden radio
|
||||||
|
await shot(page, "live-03-dark");
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 390, height: 844 });
|
||||||
|
await page.goto("/");
|
||||||
|
await shot(page, "live-04-mobile");
|
||||||
|
await page.setViewportSize({ width: 1280, height: 800 });
|
||||||
|
|
||||||
|
await page.goto(APP_SHELL);
|
||||||
|
await shot(page, "mockup-01-app-shell");
|
||||||
|
await page.goto(AUTH);
|
||||||
|
await shot(page, "mockup-02-auth");
|
||||||
|
});
|
||||||
|
|
||||||
|
// The live DOM reuses the foundation's classes, so the same styles.css must compute identically
|
||||||
|
// on both — proof we render the intended graphics, independent of the (different) row data.
|
||||||
|
const PROPS = ["backgroundColor", "borderRadius", "borderTopColor", "color", "fontSize", "fontWeight"] as const;
|
||||||
|
const styleOf = (page: Page, selector: string): Promise<Record<string, string>> =>
|
||||||
|
page.locator(selector).first().evaluate((el, props) => {
|
||||||
|
const cs = getComputedStyle(el as Element);
|
||||||
|
return Object.fromEntries(props.map((p) => [p, cs.getPropertyValue(p) || (cs as unknown as Record<string, string>)[p]]));
|
||||||
|
}, PROPS as unknown as string[]);
|
||||||
|
|
||||||
|
test("live components compute the same design-system styles as the reference mockup", async ({ page, context }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
const ref = await context.newPage();
|
||||||
|
await ref.goto(APP_SHELL);
|
||||||
|
|
||||||
|
for (const selector of [".sidebar", ".topbar", ".brand", ".btn.btn-primary", ".theme-switch", ".filters", ".pager"]) {
|
||||||
|
expect(await styleOf(page, selector), `computed style mismatch for ${selector}`).toEqual(await styleOf(ref, selector));
|
||||||
|
}
|
||||||
|
await ref.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("every icon <use> resolves to a defined <symbol> (no broken graphics)", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
const missing = await page.evaluate(() => {
|
||||||
|
const ids = new Set([...document.querySelectorAll("symbol[id]")].map((s) => s.id));
|
||||||
|
return [...document.querySelectorAll("use")]
|
||||||
|
.map((u) => (u.getAttribute("href") ?? "").replace(/^#/, ""))
|
||||||
|
.filter((id) => id && !ids.has(id));
|
||||||
|
});
|
||||||
|
expect(missing).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("sorting and search drive the list through the URL (zero-JS)", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
const total = await page.locator("tbody tr").count();
|
||||||
|
|
||||||
|
await page.getByRole("link", { name: /Name/ }).first().click();
|
||||||
|
await expect(page).toHaveURL(/sort=name/);
|
||||||
|
await expect(page.locator("thead th").filter({ hasText: "Name" })).toHaveAttribute("aria-sort", "ascending");
|
||||||
|
|
||||||
|
await page.goto("/");
|
||||||
|
await page.locator('input[name="q"]').fill("Avery");
|
||||||
|
await page.getByRole("button", { name: /Apply filters/ }).click();
|
||||||
|
await expect(page).toHaveURL(/q=Avery/);
|
||||||
|
expect(await page.locator("tbody tr").count()).toBeLessThan(total);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("theme switch flips the palette with no JavaScript", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
const light = await page.evaluate(() => getComputedStyle(document.body).backgroundColor);
|
||||||
|
await page.locator("#theme-dark").check({ force: true });
|
||||||
|
const dark = await page.evaluate(() => getComputedStyle(document.body).backgroundColor);
|
||||||
|
expect(dark).not.toBe(light);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("mobile layout hides the sidebar off-canvas behind the hamburger", async ({ page }) => {
|
||||||
|
await page.setViewportSize({ width: 390, height: 844 });
|
||||||
|
await page.goto("/");
|
||||||
|
await expect(page.locator(".hamburger")).toBeVisible();
|
||||||
|
|
||||||
|
const offCanvas = await page.locator(".sidebar").evaluate((el) => {
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
return r.right <= 1 || r.left >= window.innerWidth;
|
||||||
|
});
|
||||||
|
expect(offCanvas).toBe(true);
|
||||||
|
});
|
||||||
2
todo.md
2
todo.md
@@ -38,7 +38,7 @@ everything via Docker.
|
|||||||
- [x] Helper `parseListQuery(url)` → `{ q, filters, sort, page, pageSize }`. → `src/list-query.ts`: pure, never throws; inverse of the filter-bar GET form + sort/pagination links. Accepts `URL`/`URLSearchParams`/string. `q` trimmed; `filters` = every non-reserved param as `string[]` (multi-value chips kept, empties dropped); `sort` = `{field,dir}` with `-field` ⇒ desc (lone `-`/empty ⇒ null); `page` a positive int (else 1); `pageSize` defaults 25, clamped to [1, max 100]. Reserved names + page-size bounds overridable via options. `list-query.test.ts` covers the full/default/clamp/custom-name matrix.
|
- [x] Helper `parseListQuery(url)` → `{ q, filters, sort, page, pageSize }`. → `src/list-query.ts`: pure, never throws; inverse of the filter-bar GET form + sort/pagination links. Accepts `URL`/`URLSearchParams`/string. `q` trimmed; `filters` = every non-reserved param as `string[]` (multi-value chips kept, empties dropped); `sort` = `{field,dir}` with `-field` ⇒ desc (lone `-`/empty ⇒ null); `page` a positive int (else 1); `pageSize` defaults 25, clamped to [1, max 100]. Reserved names + page-size bounds overridable via options. `list-query.test.ts` covers the full/default/clamp/custom-name matrix.
|
||||||
- [x] Helper `paginate(total, page, pageSize)` → page model. → `src/paginate.ts`: pure, URL-free math feeding `pagination.ejs`; caller maps page numbers → hrefs. Returns `{ from, to, page, pageCount, pageSize, prev, next, total, pages }`. Inputs clamped/guarded (page pinned to [1,pageCount], total/pageSize coerced to sane ints, empty list ⇒ 1 page / 0–0). `pages` = first/last `boundaries` + `siblings`-wide window around current, sorted/deduped, with ellipsis for gaps >1 (a lone hole is shown, not collapsed); `siblings`/`boundaries` overridable. `paginate.test.ts` covers model/clamp/empty/windowing.
|
- [x] Helper `paginate(total, page, pageSize)` → page model. → `src/paginate.ts`: pure, URL-free math feeding `pagination.ejs`; caller maps page numbers → hrefs. Returns `{ from, to, page, pageCount, pageSize, prev, next, total, pages }`. Inputs clamped/guarded (page pinned to [1,pageCount], total/pageSize coerced to sane ints, empty list ⇒ 1 page / 0–0). `pages` = first/last `boundaries` + `siblings`-wide window around current, sorted/deduped, with ellipsis for gaps >1 (a lone hole is shown, not collapsed); `siblings`/`boundaries` overridable. `paginate.test.ts` covers model/clamp/empty/windowing.
|
||||||
- [x] Replace placeholder `index` with the app-shell dashboard. → `/` now renders a real app-shell "People" list. `src/dashboard.ts` (pure `buildDashboardModel(url, roles)`) wires the §1 helpers end-to-end: `parseListQuery` → filter (q/status/team) + sort + `paginate` over a 30-row mock dataset → `composeNav`; builds the filter-bar/data-table/pagination/shell configs with canonical, state-preserving links. `views/index.ejs` composes the partials around the shell by capturing each `include()` (EJS returns the string) into a slot. Filtering/sorting/paging all round-trip the URL, zero-JS. Removed the dead `partials/header.ejs`. `dashboard.test.ts` covers default/search/sort/paginate; `app.test.ts` asserts the live page + URL filtering. Mock data + demo profile stand in until §2/§4.
|
- [x] Replace placeholder `index` with the app-shell dashboard. → `/` now renders a real app-shell "People" list. `src/dashboard.ts` (pure `buildDashboardModel(url, roles)`) wires the §1 helpers end-to-end: `parseListQuery` → filter (q/status/team) + sort + `paginate` over a 30-row mock dataset → `composeNav`; builds the filter-bar/data-table/pagination/shell configs with canonical, state-preserving links. `views/index.ejs` composes the partials around the shell by capturing each `include()` (EJS returns the string) into a slot. Filtering/sorting/paging all round-trip the URL, zero-JS. Removed the dead `partials/header.ejs`. `dashboard.test.ts` covers default/search/sort/paginate; `app.test.ts` asserts the live page + URL filtering. Mock data + demo profile stand in until §2/§4.
|
||||||
- [ ] Check the full system in Playwright and make screenshots and compare to the static original design in html-css-foundation to make sure we're showing the correct graphics.
|
- [x] Check the full system in Playwright and make screenshots and compare to the static original design in html-css-foundation to make sure we're showing the correct graphics. → Dockerized Playwright (official image, browsers preinstalled — no host Node/browsers): `e2e/` (config + `visual.spec.ts`), `Dockerfile.e2e`, `compose.e2e.yml` run the suite against the live `web` service. 6 parallel tests: screenshots live (default/sorted+filtered/dark/mobile) **and** the foundation mockups (App Shell + Auth) → `e2e/artifacts/` (git-ignored); asserts the live DOM computes the **same** design-system styles as the mockup for the shared components (`.sidebar/.topbar/.brand/.btn-primary/.theme-switch/.filters/.pager`), every icon `<use>` resolves, sort/search round-trip the URL, the CSS theme switch flips the palette, and mobile hides the sidebar off-canvas. Verified visually: live dashboard matches the mockup design (light + dark); diffs are data only. All green.
|
||||||
- [ ] Go over all HTML and CSS and make adjust it to be as sematic as we can, css classes, ids html elements and all, then add semantic DOM as a priority in this project.
|
- [ ] Go over all HTML and CSS and make adjust it to be as sematic as we can, css classes, ids html elements and all, then add semantic DOM as a priority in this project.
|
||||||
- [ ] Run the architecture _and_ the stability reviewer agents on the _whole_ project, not just the latest changes, and address their issues.
|
- [ ] Run the architecture _and_ the stability reviewer agents on the _whole_ project, not just the latest changes, and address their issues.
|
||||||
- [ ] Go over all comments in the code and the README and try to make it shorter and more information dense. Remove not strictly needed stuff.
|
- [ ] Go over all comments in the code and the README and try to make it shorter and more information dense. Remove not strictly needed stuff.
|
||||||
|
|||||||
Reference in New Issue
Block a user