@jrpool/kilotest 60.1.1 → 60.1.3

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.
Files changed (59) hide show
  1. package/.htmlhintrc +29 -0
  2. package/error.html +1 -5
  3. package/index.test.ts +8 -2
  4. package/index.ts +5 -5
  5. package/lhm.plugin.json +1 -1
  6. package/linkValidation.test.ts +1 -0
  7. package/logo.svg +15 -0
  8. package/openapi-3.0.yaml +1 -1
  9. package/openapi.yaml +1 -1
  10. package/package.json +3 -2
  11. package/server.json +1 -1
  12. package/smokeTest.ts +1 -1
  13. package/style.css +199 -0
  14. package/util.ts +22 -3
  15. package/web/ai0BalanceForm/index.html +1 -6
  16. package/web/deleteNotesForm/index.html +3 -8
  17. package/web/deleteNotesForm/index.ts +1 -1
  18. package/web/enqueue/index.html +1 -6
  19. package/web/enqueueForm/index.html +3 -8
  20. package/web/enqueueForm/index.ts +1 -1
  21. package/web/expungeReportsForm/index.html +3 -8
  22. package/web/head.partial +6 -0
  23. package/web/hideReportForm/index.html +1 -6
  24. package/web/home/home.test.ts +1 -1
  25. package/web/home/index.html +53 -41
  26. package/web/home/index.ts +2 -3
  27. package/web/listDiagnoses/index.html +1 -6
  28. package/web/listIssues/index.html +1 -6
  29. package/web/listPages/index.html +1 -6
  30. package/web/listRules/index.html +1 -6
  31. package/web/listTopIssues/index.html +1 -6
  32. package/web/listViolators/index.html +1 -6
  33. package/web/manage/index.html +1 -6
  34. package/web/manage/index.ts +2 -3
  35. package/web/metrics/index.html +1 -6
  36. package/web/privacy/index.html +1 -6
  37. package/web/privacy/index.ts +2 -3
  38. package/web/pruneReportsForm/index.html +3 -8
  39. package/web/reannotate/index.html +1 -6
  40. package/web/reannotate/index.ts +2 -2
  41. package/web/reannotateForm/index.html +1 -6
  42. package/web/renewWCAG/index.html +1 -6
  43. package/web/renewWCAG/index.ts +2 -2
  44. package/web/renewWCAGForm/index.html +2 -7
  45. package/web/renewWCAGForm/index.ts +2 -3
  46. package/web/reportDeletion.ts +1 -1
  47. package/web/requestNewTest/index.html +1 -6
  48. package/web/requestNewTestForm/index.html +1 -6
  49. package/web/requestNewTestForm/index.ts +2 -3
  50. package/web/requestRetest/index.html +1 -6
  51. package/web/requestRetestForm/index.html +1 -6
  52. package/web/rewindReportsForm/index.html +3 -8
  53. package/web/showHiddenReportsForm/index.html +2 -7
  54. package/web/tutorialAI/index.html +1 -6
  55. package/web/tutorialAI/index.ts +9 -2
  56. package/web/tutorialWeb/index.html +1 -6
  57. package/web/tutorialWeb/index.ts +9 -2
  58. package/web/unhideReportForm/index.html +2 -7
  59. package/favicon.ico +0 -0
package/.htmlhintrc ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "doctype-first": true,
3
+ "doctype-html5": true,
4
+ "title-require": true,
5
+ "tagname-lowercase": true,
6
+ "tag-pair": true,
7
+ "tags-check": false,
8
+ "attr-lowercase": true,
9
+ "attr-value-double-quotes": true,
10
+ "attr-no-duplication": true,
11
+ "attr-unsafe-chars": true,
12
+ "attr-whitespace": true,
13
+ "id-unique": true,
14
+ "src-not-empty": true,
15
+ "spec-char-escape": true,
16
+ "alt-require": true,
17
+ "space-tab-mixed-disabled": "space",
18
+ "attr-value-not-empty": false,
19
+ "tag-self-close": false,
20
+ "head-script-disabled": false,
21
+ "script-disabled": false,
22
+ "style-disabled": false,
23
+ "inline-style-disabled": false,
24
+ "inline-script-disabled": false,
25
+ "href-abs-or-rel": false,
26
+ "attr-sorted": false,
27
+ "id-class-ad-disabled": false,
28
+ "id-class-value": false
29
+ }
package/error.html CHANGED
@@ -1,13 +1,9 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en-US">
3
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <meta name="publisher" content="Jonathan Robert Pool">
7
- <meta name="creator" content="Jonathan Robert Pool">
4
+ __head__
8
5
  <meta name="keywords" content="report,accessibility,a11y">
9
6
  <title>Error | Kilotest</title>
10
- <link rel="stylesheet" href="/style.css">
11
7
  </head>
12
8
  <body>
13
9
  <main>
package/index.test.ts CHANGED
@@ -326,10 +326,16 @@ test('GET /style.css serves the stylesheet as CSS', async () => {
326
326
  assert.ok(res.headers['content-type'].includes('text/css'));
327
327
  });
328
328
 
329
- test('GET /favicon.ico serves the favicon as an icon', async () => {
329
+ test('GET /favicon.ico serves the logo as SVG', async () => {
330
330
  const res = await request('GET', '/favicon.ico');
331
331
  assert.equal(res.statusCode, 200);
332
- assert.ok(res.headers['content-type'].includes('image/x-icon'));
332
+ assert.ok(res.headers['content-type'].includes('image/svg+xml'));
333
+ });
334
+
335
+ test('GET /favicon.svg serves the logo as SVG', async () => {
336
+ const res = await request('GET', '/favicon.svg');
337
+ assert.equal(res.statusCode, 200);
338
+ assert.ok(res.headers['content-type'].includes('image/svg+xml'));
333
339
  });
334
340
 
335
341
  test('GET /api/listPages returns JSON with report data and records an API-operation metric', async () => {
package/index.ts CHANGED
@@ -29,6 +29,7 @@ import {
29
29
  isValidAuthCode,
30
30
  jobsPath,
31
31
  metricsExclusionCookieName,
32
+ populateFile,
32
33
  recordMetric,
33
34
  reportsPath,
34
35
  unusableReportsPath
@@ -367,9 +368,8 @@ export const serveError = async (error: Record<string, unknown>, response: Serve
367
368
  response.setHeader('content-location', '/error.html');
368
369
  response.setHeader('Access-Control-Allow-Origin', '*');
369
370
  response.setHeader('Cache-Control', 'public, max-age=300, stale-while-revalidate=3000');
370
- const errorTemplate = await fs.readFile('error.html', 'utf8');
371
371
  const errorMessage = typeof error.message === 'string' ? error.message : 'ERROR';
372
- const errorPage = errorTemplate.replace(/__error__/, errorMessage);
372
+ const errorPage = await populateFile('error.html', {error: errorMessage});
373
373
  response.end(errorPage);
374
374
  }
375
375
  // Otherwise, i.e. if it is from an agent:
@@ -942,10 +942,10 @@ const handleRequest = async (request: IncomingMessage, response: ServerResponse)
942
942
  }
943
943
  // Otherwise, if it is for the application icon:
944
944
  else if (pathname.includes('favicon.')) {
945
- // Get the site icon.
946
- const icon = await fs.readFile(path.join(import.meta.dirname, 'favicon.ico'));
945
+ // Get the site icon (logo.svg, the only format served).
946
+ const icon = await fs.readFile(path.join(import.meta.dirname, 'logo.svg'));
947
947
  // Serve it.
948
- setHeaders('image/x-icon', null, 'low');
948
+ setHeaders('image/svg+xml', null, 'low');
949
949
  response.write(icon, 'binary');
950
950
  response.end('');
951
951
  }
package/lhm.plugin.json CHANGED
@@ -4423,5 +4423,5 @@
4423
4423
  }
4424
4424
  }
4425
4425
  ],
4426
- "version": "60.1.1"
4426
+ "version": "60.1.3"
4427
4427
  }
@@ -16,6 +16,7 @@ const staticFiles = [
16
16
  '/index.html',
17
17
  '/style.css',
18
18
  '/favicon.ico',
19
+ '/favicon.svg',
19
20
  '/openapi.yaml',
20
21
  '/openapi.json',
21
22
  '/capability.md',
package/logo.svg ADDED
@@ -0,0 +1,15 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-hidden="true">
2
+ <style>
3
+ .lens { stroke: #086b6b; }
4
+ .zero { fill: #12a05c; }
5
+ @media (prefers-color-scheme: dark) {
6
+ .lens { stroke: #3fb8b8; }
7
+ .zero { fill: #4ce0a0; }
8
+ }
9
+ </style>
10
+ <circle class="lens" cx="14" cy="14" r="8.6" fill="none" stroke-width="2.6"/>
11
+ <path class="lens" d="M20.3 20.3L26.8 26.8" fill="none" stroke-width="2.8" stroke-linecap="round"/>
12
+ <ellipse class="zero" cx="9.9" cy="14" rx="1.5" ry="1.9"/>
13
+ <ellipse class="zero" cx="14" cy="14" rx="1.5" ry="1.9"/>
14
+ <ellipse class="zero" cx="18.1" cy="14" rx="1.5" ry="1.9"/>
15
+ </svg>
package/openapi-3.0.yaml CHANGED
@@ -2,7 +2,7 @@ openapi: 3.0.3
2
2
  info:
3
3
  title: Kilotest API
4
4
  description: 'Kilotest is an ensemble testing and reporting service for front-end web quality. It uses an ensemble of 12 rule engines to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
5
- version: 60.1.1
5
+ version: 60.1.3
6
6
  license:
7
7
  name: MIT
8
8
  url: https://github.com/jrpool/kilotest/blob/main/LICENSE
package/openapi.yaml CHANGED
@@ -2,7 +2,7 @@ openapi: 3.1.0
2
2
  info:
3
3
  title: Kilotest API
4
4
  description: 'Kilotest is an ensemble testing and reporting service for front-end web quality. It uses an ensemble of 12 rule engines to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
5
- version: 60.1.1
5
+ version: 60.1.3
6
6
  license:
7
7
  name: MIT
8
8
  identifier: MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jrpool/kilotest",
3
- "version": "60.1.1",
3
+ "version": "60.1.3",
4
4
  "description": "Ensemble testing for front-end web quality",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -10,7 +10,7 @@
10
10
  "scripts": {
11
11
  "start": "node index.ts",
12
12
  "typecheck": "tsc --noEmit",
13
- "lint": "npx eslint && out=$(npx markdownlint-cli2 2>&1); code=$?; if [ $code -ne 0 ]; then echo \"$out\"; exit $code; fi",
13
+ "lint": "npx eslint && npx htmlhint web error.html && out=$(npx markdownlint-cli2 2>&1); code=$?; if [ $code -ne 0 ]; then echo \"$out\"; exit $code; fi",
14
14
  "test": "c8 node --require ./test/setup.ts --experimental-test-module-mocks --test --test-concurrency=1 --test-randomize '**/*.test.ts'",
15
15
  "test:smoke": "node smokeTest.ts",
16
16
  "checkinfo": "node checkinfo.ts",
@@ -55,6 +55,7 @@
55
55
  "c8": "*",
56
56
  "eslint": "*",
57
57
  "globals": "*",
58
+ "htmlhint": "^1.9.2",
58
59
  "js-yaml": "*",
59
60
  "markdownlint-cli2": "^0.23.2",
60
61
  "node-html-parser": "*",
package/server.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "https://github.com/jrpool/kilotest",
7
7
  "source": "github"
8
8
  },
9
- "version": "60.1.1",
9
+ "version": "60.1.3",
10
10
  "remotes": [
11
11
  {
12
12
  "type": "streamable-http",
package/smokeTest.ts CHANGED
@@ -19,7 +19,7 @@ const concretePaths: Record<string, Record<string, string>> = {
19
19
  '/api-docs': '/api-docs',
20
20
  '/api/*': '/api/listPages',
21
21
  '/capability.md': '/capability.md',
22
- '/favicon.*': '/favicon.ico',
22
+ '/favicon.*': '/favicon.svg',
23
23
  '/fullReport.json/*': '/fullReport.json/260101T0000/mix',
24
24
  '/index.html': '/index.html',
25
25
  '/llms-full.txt': '/llms-full.txt',
package/style.css CHANGED
@@ -208,3 +208,202 @@ ul.xPathList li {
208
208
  padding-left: 0;
209
209
  overflow-wrap: break-word;
210
210
  }
211
+ body.home {
212
+ --ink: #18212b;
213
+ --muted: #4c5a66;
214
+ --accent: #086b6b;
215
+ --accent-dark: #064f4f;
216
+ --bg: #f6f8f8;
217
+ --card: #fff;
218
+ --line: #dde4e4;
219
+ --focus: #005fcc;
220
+ margin: 0;
221
+ font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
222
+ font-size: 1.0625rem;
223
+ line-height: 1.6;
224
+ color: var(--ink);
225
+ background: var(--bg);
226
+ }
227
+ body.home, body.home *, body.home *::before, body.home *::after {
228
+ box-sizing: border-box;
229
+ }
230
+ body.home a {
231
+ color: var(--accent);
232
+ text-underline-offset: 0.15em;
233
+ }
234
+ body.home a:hover {
235
+ color: var(--accent-dark);
236
+ background-color: transparent;
237
+ }
238
+ body.home a:focus-visible, body.home summary:focus-visible {
239
+ outline: 3px solid var(--focus);
240
+ outline-offset: 2px;
241
+ border-radius: 4px;
242
+ }
243
+ body.home h2 {
244
+ color: var(--ink);
245
+ }
246
+ body.home li, body.home p {
247
+ line-height: 1.6;
248
+ }
249
+ body.home p {
250
+ margin: 0.6rem 0;
251
+ }
252
+ body.home header {
253
+ background: var(--card);
254
+ border-bottom: 1px solid var(--line);
255
+ }
256
+ body.home .header-inner {
257
+ max-width: 64rem;
258
+ margin-inline: auto;
259
+ padding: 1.1rem 1.5rem;
260
+ display: flex;
261
+ align-items: baseline;
262
+ gap: 0.75rem;
263
+ }
264
+ body.home .brand {
265
+ display: inline-flex;
266
+ align-items: center;
267
+ gap: 0.55rem;
268
+ font-weight: 800;
269
+ font-size: 1.25rem;
270
+ letter-spacing: -0.02em;
271
+ }
272
+ body.home .brand svg {
273
+ width: 1.35rem;
274
+ height: 1.35rem;
275
+ }
276
+ body.home .brand-tag {
277
+ font-size: 0.85rem;
278
+ color: var(--muted);
279
+ }
280
+ body.home .hero {
281
+ max-width: 64rem;
282
+ margin-inline: auto;
283
+ padding: 4rem 1.5rem 0;
284
+ }
285
+ body.home .eyebrow {
286
+ margin: 0 0 0.75rem;
287
+ font-size: 0.85rem;
288
+ font-weight: 700;
289
+ letter-spacing: 0.04em;
290
+ color: var(--accent);
291
+ }
292
+ body.home .eyebrow::before {
293
+ content: "";
294
+ display: inline-block;
295
+ width: 1.4rem;
296
+ height: 0.16rem;
297
+ margin: 0 0.5rem 0.22rem 0;
298
+ background: var(--accent);
299
+ }
300
+ body.home .hero h1 {
301
+ margin: 0 0 1rem;
302
+ max-width: 24ch;
303
+ font-size: clamp(2rem, 4.5vw, 3.1rem);
304
+ line-height: 1.12;
305
+ letter-spacing: -0.03em;
306
+ }
307
+ body.home .hero .lede {
308
+ margin: 0 0 1.75rem;
309
+ max-width: 52ch;
310
+ font-size: 1.2rem;
311
+ color: var(--muted);
312
+ }
313
+ body.home details {
314
+ max-width: 52rem;
315
+ margin: 0 0 1rem;
316
+ padding: 0;
317
+ background: var(--card);
318
+ border: 1px solid var(--line);
319
+ border-radius: 0.75rem;
320
+ }
321
+ body.home summary {
322
+ min-height: 44px;
323
+ padding: 0.9rem 1.25rem;
324
+ font-weight: 600;
325
+ cursor: pointer;
326
+ }
327
+ body.home summary:hover {
328
+ background: #eef3f3;
329
+ border-radius: 0.75rem;
330
+ }
331
+ body.home details[open] summary {
332
+ border-bottom: 1px solid var(--line);
333
+ border-radius: 0.75rem 0.75rem 0 0;
334
+ }
335
+ body.home details > :not(summary) {
336
+ margin-inline: 1.5rem;
337
+ }
338
+ body.home details h2 {
339
+ margin: 1.5rem 0 0.5rem;
340
+ font-size: 1.3rem;
341
+ }
342
+ body.home details h3 {
343
+ margin: 1.35rem 0 0.35rem;
344
+ font-size: 1.08rem;
345
+ }
346
+ body.home .nav-section {
347
+ max-width: 64rem;
348
+ margin-inline: auto;
349
+ padding: 2.25rem 1.5rem 0;
350
+ }
351
+ body.home .nav-section:last-child {
352
+ padding-bottom: 5rem;
353
+ }
354
+ body.home .nav-section h2 {
355
+ margin: 0 0 1.25rem;
356
+ font-size: 1.4rem;
357
+ letter-spacing: -0.01em;
358
+ }
359
+ body.home ul.nav {
360
+ margin: 0;
361
+ padding: 0;
362
+ list-style: none;
363
+ display: grid;
364
+ grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
365
+ gap: 1rem;
366
+ }
367
+ body.home ul.nav a {
368
+ display: block;
369
+ min-height: 44px;
370
+ height: 100%;
371
+ padding: 1.2rem 1.35rem;
372
+ background: var(--card);
373
+ border: 1px solid var(--line);
374
+ border-radius: 0.75rem;
375
+ text-decoration: none;
376
+ transition: background-color 0.12s ease-out, border-color 0.12s ease-out;
377
+ }
378
+ body.home ul.nav a:hover {
379
+ background: #eef4f4;
380
+ border-color: var(--accent);
381
+ }
382
+ body.home .card-title {
383
+ font-weight: 700;
384
+ font-size: 1.05rem;
385
+ color: var(--accent);
386
+ text-decoration: underline;
387
+ text-decoration-thickness: 0.09em;
388
+ text-underline-offset: 0.2em;
389
+ }
390
+ body.home .card-title::after {
391
+ content: "\00a0→";
392
+ display: inline-block;
393
+ text-decoration: none;
394
+ font-weight: 700;
395
+ }
396
+ body.home ul.nav a:hover .card-title {
397
+ text-decoration-thickness: 0.18em;
398
+ }
399
+ body.home .card-desc {
400
+ display: block;
401
+ margin: 0.4rem 0 0;
402
+ font-size: 0.9rem;
403
+ color: var(--muted);
404
+ }
405
+ @media (prefers-reduced-motion: reduce) {
406
+ body.home ul.nav a {
407
+ transition: none;
408
+ }
409
+ }
package/util.ts CHANGED
@@ -375,10 +375,26 @@ export const getNowStamp = (): string => {
375
375
  export const getPlainText = (string: string): string => string
376
376
  .replace(/&/g, '+')
377
377
  .replace(/[<>"']/g, ' ');
378
- // Populates an index.html template in a directory with named values.
379
- export const populateTemplate = async (dirName: string, query: Record<string, string>): Promise<string> => {
378
+ // Markup shared by populated templates, lazily read once from files.
379
+ let sharedContent: {head: string, logo: string} | undefined;
380
+ // Returns the markup shared by populated templates.
381
+ const getSharedContent = async (): Promise<{head: string, logo: string}> => {
382
+ // Trailing newlines are stripped so injection does not add blank lines.
383
+ sharedContent ??= {
384
+ head: (await fs.readFile(path.join(import.meta.dirname, 'web', 'head.partial'), 'utf8')).trimEnd(),
385
+ logo: (await fs.readFile(path.join(import.meta.dirname, 'logo.svg'), 'utf8')).trimEnd()
386
+ };
387
+ return sharedContent;
388
+ };
389
+ // Populates an HTML template file with named values.
390
+ export const populateFile = async (filePath: string, query: Record<string, string>): Promise<string> => {
380
391
  // Get the template.
381
- let answerPage = await fs.readFile(path.join(dirName, 'index.html'), 'utf8');
392
+ let answerPage = await fs.readFile(filePath, 'utf8');
393
+ // Inject the markup shared by all templates.
394
+ const shared = await getSharedContent();
395
+ answerPage = answerPage
396
+ .replace(/__head__/g, () => shared.head)
397
+ .replace(/__logo__/g, () => shared.logo);
382
398
  // Replace its placeholders.
383
399
  Object.keys(query).forEach(param => {
384
400
  // A replacer function keeps $-patterns in a value from being interpreted.
@@ -386,6 +402,9 @@ export const populateTemplate = async (dirName: string, query: Record<string, st
386
402
  });
387
403
  return answerPage;
388
404
  };
405
+ // Populates an index.html template in a directory with named values.
406
+ export const populateTemplate = (dirName: string, query: Record<string, string>): Promise<string> =>
407
+ populateFile(path.join(dirName, 'index.html'), query);
389
408
  // Returns the data from a POST request. Resolves null for an unrecognized content-type or a
390
409
  // malformed JSON body; callers treat null as an unreadable request.
391
410
  export const getPOSTData = (request: import('node:http').IncomingMessage): Promise<unknown> => new Promise(resolve => {
@@ -1,14 +1,9 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en-US">
3
3
  <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <meta name="publisher" content="Jonathan Robert Pool">
7
- <meta name="creator" content="Jonathan Robert Pool">
4
+ __head__
8
5
  <meta name="keywords" content="report,accessibility,a11y">
9
6
  <title>Record AI service 0 balance | Kilotest</title>
10
- <link rel="icon" href="/favicon.ico">
11
- <link rel="stylesheet" href="/style.css">
12
7
  </head>
13
8
  <body>
14
9
  <main>
@@ -1,14 +1,9 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en-US">
3
3
  <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <meta name="publisher" content="Jonathan Robert Pool">
7
- <meta name="creator" content="Jonathan Robert Pool">
4
+ __head__
8
5
  <meta name="keywords" content="report,accessibility,a11y">
9
6
  <title>Delete notes | Kilotest</title>
10
- <link rel="icon" href="/favicon.ico">
11
- <link rel="stylesheet" href="/style.css">
12
7
  </head>
13
8
  <body>
14
9
  <main>
@@ -20,9 +15,9 @@
20
15
  __notes__
21
16
  </fieldset>
22
17
  <p><label>
23
- Authorization code: <input size="3" minLength="3" maxlength="3" name="authCode" required__disabled__>
18
+ Authorization code: <input size="3" minlength="3" maxlength="3" name="authCode" required __disabled__>
24
19
  </label></p>
25
- <p><button type="submit"__disabled__>Submit</button></p>
20
+ <p><button type="submit" __disabled__>Submit</button></p>
26
21
  </form>
27
22
  </main>
28
23
  </body>
@@ -164,7 +164,7 @@ export const answer = async (
164
164
  intro: anyNotes ?
165
165
  'Choose the comments and feature requests to delete.' :
166
166
  'There are no comments or feature requests to delete.',
167
- disabled: anyNotes ? '' : ' disabled'
167
+ disabled: anyNotes ? '' : 'disabled'
168
168
  };
169
169
  // Return the populated page.
170
170
  return {
@@ -1,14 +1,9 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en-US">
3
3
  <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <meta name="publisher" content="Jonathan Robert Pool">
7
- <meta name="creator" content="Jonathan Robert Pool">
4
+ __head__
8
5
  <meta name="keywords" content="report,accessibility,a11y">
9
6
  <title>Test order | Kilotest</title>
10
- <link rel="icon" href="/favicon.ico">
11
- <link rel="stylesheet" href="/style.css">
12
7
  </head>
13
8
  <body>
14
9
  <main>
@@ -1,14 +1,9 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en-US">
3
3
  <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <meta name="publisher" content="Jonathan Robert Pool">
7
- <meta name="creator" content="Jonathan Robert Pool">
4
+ __head__
8
5
  <meta name="keywords" content="report,accessibility,a11y">
9
6
  <title>Request approval or rejection | Kilotest</title>
10
- <link rel="icon" href="/favicon.ico">
11
- <link rel="stylesheet" href="/style.css">
12
7
  </head>
13
8
  <body>
14
9
  <main>
@@ -24,9 +19,9 @@
24
19
  </fieldset>
25
20
  <p><label>
26
21
  Authorization code:
27
- <input size="3" minLength="3" maxlength="3" name="authCode" required__disabled__>
22
+ <input size="3" minlength="3" maxlength="3" name="authCode" required __disabled__>
28
23
  </label></p>
29
- <p><button type="submit"__disabled__>Submit</button></p>
24
+ <p><button type="submit" __disabled__>Submit</button></p>
30
25
  </form>
31
26
  </main>
32
27
  </body>
@@ -31,7 +31,7 @@ export const answer = async () => {
31
31
  const query: Record<string, string> = {
32
32
  requests: lines.join('\n'),
33
33
  noRequests: urls.length ? '' : 'No requests exist now.',
34
- disabled: urls.length ? '' : ' disabled'
34
+ disabled: urls.length ? '' : 'disabled'
35
35
  };
36
36
  // Get the populated order form template.
37
37
  const answerPage = await populateTemplate(import.meta.dirname, query);
@@ -1,14 +1,9 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en-US">
3
3
  <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <meta name="publisher" content="Jonathan Robert Pool">
7
- <meta name="creator" content="Jonathan Robert Pool">
4
+ __head__
8
5
  <meta name="keywords" content="report,accessibility,a11y">
9
6
  <title>Expunge reports | Kilotest</title>
10
- <link rel="icon" href="/favicon.ico">
11
- <link rel="stylesheet" href="/style.css">
12
7
  </head>
13
8
  <body>
14
9
  <main>
@@ -20,9 +15,9 @@
20
15
  __reports__
21
16
  </fieldset>
22
17
  <p><label>
23
- Authorization code: <input size="3" minLength="3" maxlength="3" name="authCode" required__disabled__>
18
+ Authorization code: <input size="3" minlength="3" maxlength="3" name="authCode" required __disabled__>
24
19
  </label></p>
25
- <p><button type="submit"__disabled__>Submit</button></p>
20
+ <p><button type="submit" __disabled__>Submit</button></p>
26
21
  </form>
27
22
  </main>
28
23
  </body>
@@ -0,0 +1,6 @@
1
+ <meta charset="utf-8">
2
+ <meta name="viewport" content="width=device-width, initial-scale=1">
3
+ <meta name="publisher" content="Jonathan Robert Pool">
4
+ <meta name="creator" content="Jonathan Robert Pool">
5
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml">
6
+ <link rel="stylesheet" href="/style.css">
@@ -1,14 +1,9 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en-US">
3
3
  <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <meta name="publisher" content="Jonathan Robert Pool">
7
- <meta name="creator" content="Jonathan Robert Pool">
4
+ __head__
8
5
  <meta name="keywords" content="report,accessibility,a11y">
9
6
  <title>Classify report as experimental | Kilotest</title>
10
- <link rel="icon" href="/favicon.ico">
11
- <link rel="stylesheet" href="/style.css">
12
7
  </head>
13
8
  <body>
14
9
  <main>
@@ -14,6 +14,6 @@ import {answer} from './index.ts';
14
14
  test('answer returns the home page', async () => {
15
15
  const result = await answer();
16
16
  assert.equal(result.status, 'ok');
17
- assert.ok(result.answerPage.includes('<h1>Kilotest</h1>'));
17
+ assert.ok(result.answerPage.includes('More than a thousand tests'));
18
18
  assert.ok(result.answerPage.includes('application/ld+json'));
19
19
  });