Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
aa3b7e0
chore(repo): move to spec 0.3.0, which defines the postcode field
MobileMage Oct 3, 2026
46ce038
feat(field): read the text of a postcode field and hold its messages
MobileMage Oct 3, 2026
2049a76
feat(field): add the postcode field element for plain forms
MobileMage Oct 3, 2026
f7ec99e
fix(field): harden placeholders, the input count and the generator
MobileMage Oct 3, 2026
d34b34d
fix(field): submit like a native input, and keep the live region quiet
MobileMage Oct 3, 2026
531846e
feat(field): check each whole postcode with NIPOST when a key is set
MobileMage Oct 3, 2026
6459e42
fix(field): ignore a default button in a disabled fieldset
MobileMage Oct 3, 2026
f9c9e7b
feat(field): fill in the postcode of the user's location on request
MobileMage Oct 3, 2026
7f4e6ec
fix(field): end every failed call as a failed request
MobileMage Oct 3, 2026
8a6d809
fix(field): raise network_error for an error that is not the client's
MobileMage Oct 3, 2026
07aa85e
fix(field): end every failed location call, and split the request rules
MobileMage Oct 3, 2026
0d06a73
test(field): run the field in a plain form in three browser engines
MobileMage Oct 3, 2026
6d1e0eb
fix(field): cancel a location request when the text is rewritten
MobileMage Oct 3, 2026
fbf0dd1
fix(field): take properties set before the element is defined
MobileMage Oct 3, 2026
76414d4
test(field): stop stray requests, check the theme and the back button
MobileMage Oct 3, 2026
e3f256e
feat(react): add the postcode field as a React component
MobileMage Oct 3, 2026
8192fe0
fix(react): keep defaultValue as the first text on React 19
MobileMage Oct 3, 2026
7ff0465
fix(field): keep a location request, and refuse a padded secret key
MobileMage Oct 3, 2026
785f31d
fix(field): take null messages as none, and test the dark page for real
MobileMage Oct 3, 2026
015de2c
Merge origin/main into feat/field
MobileMage Oct 3, 2026
fb1eb6b
docs(repo): list the field and React packages in the root README
MobileMage Oct 3, 2026
805ff03
docs(field): add the header block and the order of the other READMEs
MobileMage Oct 3, 2026
e71d54e
docs(react): add the header block and the order of the other READMEs
MobileMage Oct 3, 2026
c68eeef
chore(repo): move the spec to its merged 0.3.0 commit
MobileMage Oct 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/client-spec-0-3-0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@gatepost/client': patch
---

The package implements spec 0.3.0. That version adds the postcode field and its messages, and leaves the client contract as it was, so every call behaves as before.
5 changes: 5 additions & 0 deletions .changeset/core-spec-0-3-0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@gatepost/core': patch
---

The package implements spec 0.3.0. That version adds the postcode field and its messages, and leaves the grammar as it was, so every function gives the same results as before.
5 changes: 5 additions & 0 deletions .changeset/first-field-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@gatepost/field': minor
---

Add the custom element `gatepost-postcode-field`. A plain HTML form submits the canonical form of the postcode. The field checks the format offline, offers the fix of a look-alike character, accepts or refuses a legacy postcode, and works with the keyboard alone. With a publishable key, it asks NIPOST's gateway about each whole postcode and names its place. A failed or negative answer never stops the form. The field refuses a secret key and sends no request with it. With `gps` and a key, it fills in the postcode of the user's location, or only the part that the accuracy supports, when the user presses its button.
5 changes: 5 additions & 0 deletions .changeset/first-react-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@gatepost/react': minor
---

Add the React package. Its component renders the postcode field of `@gatepost/field` on the server and in the browser, for React 18 and 19, and calls a prop for each event of the field.
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ jobs:
enable-cache: false
- run: corepack enable
- run: pnpm install --frozen-lockfile
# The tests of the field run in Chromium.
- run: pnpm exec playwright install --with-deps chromium
- run: pnpm check

node-22:
Expand All @@ -48,8 +50,42 @@ jobs:
node-version: 22
- run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm exec playwright install --with-deps chromium
- run: pnpm test

# pnpm check runs the journeys of the field in Chromium. This job runs them in the other two
# engines, so a page in Firefox or Safari gets the same field.
browsers:
runs-on: ubuntu-24.04
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
browser: [firefox, webkit]
env:
BROWSER: ${{ matrix.browser }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: true
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
- run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm exec playwright install --with-deps "$BROWSER"
- run: pnpm build
- run: pnpm --filter @gatepost/field exec playwright test --project="$BROWSER"
# A failure in an engine that nobody runs by hand needs its trace.
- if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: traces-${{ matrix.browser }}
path: packages/field/test-results/
if-no-files-found: ignore
retention-days: 7

commits:
if: github.event_name == 'pull_request'
runs-on: ubuntu-24.04
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@ dist/
coverage/
temp/
reports/
test-results/
playwright-report/
.stryker-tmp/
*.tsbuildinfo
.vitest/
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ spec
**/temp
**/etc/*.api.md
**/spec-data.ts
**/spec-messages.ts
**/CHANGELOG.md
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AGENTS.md

This repo is part of Gatepost, unofficial open-source developer tools for Nigeria's National Digital Postcode. This repo holds the TypeScript packages. Today it has `@gatepost/core`, `@gatepost/client` and the private `@gatepost/mock-server`.
This repo is part of Gatepost, unofficial open-source developer tools for Nigeria's National Digital Postcode. This repo holds the TypeScript packages. Today it has `@gatepost/core`, `@gatepost/client`, `@gatepost/field`, `@gatepost/react` and the private `@gatepost/mock-server`.

## Read first

Expand All @@ -10,7 +10,7 @@ This repo is part of Gatepost, unofficial open-source developer tools for Nigeri

## Rules that no config file shows

- If the task needs a public symbol that the Interface section of `spec/grammar.md` does not list, add nothing, not even an internal helper. Stop, and say that a spec change must come first (API-1, CS-8).
- If the task needs a public symbol that no Interface section of the spec lists, in `spec/grammar.md`, `spec/client.md` or `spec/field.md`, add nothing, not even an internal helper. Stop, and say that a spec change must come first (API-1, CS-8).
- Call only the documented NIPOST gateway endpoints.
- Commit only NIPOST's published test codes and synthetic values, such as `FC-01-Z99-ZZ-01`.
- Sign off each commit with `git commit -s`. The person who opens the pull request is the only author. Leave out co-author lines for AI tools.
Expand Down
12 changes: 12 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,18 @@ _Avoid_: input, widget, picker
To show the user what a postcode points to before the app stores it.
_Avoid_: verify, validate (for this step)

**Check status**:
What the WooCommerce plugin learned about the postcode of an order: `valid`, `invalid`, `unchecked` or `error`. The order meta `_gatepost_postcode_check` holds it.
_Avoid_: verification status, validation result

**Form value**:
The text that a field gives its form when the form is sent: the canonical form of a postcode, the 6 digits of a legacy postcode, or the text without white space at the start and the end.
_Avoid_: submitted value, output

**Message catalogue**:
A data file that holds the text of a UI in one language, one message for each key.
_Avoid_: strings file, translations, locale file

**Tell**:
A habit that makes code look machine-written, such as very long lines.
_Avoid_: smell (smells are design problems), anti-pattern
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
| ----------------------------------------------- | ----------------------------------------------- | -------------------------- |
| [`@gatepost/core`](packages/core) | Parse, check and format postcodes offline. | Alpha, not on npm yet |
| [`@gatepost/client`](packages/client) | Call NIPOST's gateway, with retries and limits. | Alpha, not on npm yet |
| [`@gatepost/field`](packages/field) | A postcode field for any web form. | Alpha, not on npm yet |
| [`@gatepost/react`](packages/react) | The postcode field as a React component. | Alpha, not on npm yet |
| [`@gatepost/mock-server`](packages/mock-server) | Mock NIPOST's gateway for client tests. | Private, never goes to npm |

## Example
Expand Down
5 changes: 5 additions & 0 deletions docs/develop.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ You need Node 24, `corepack enable`, Python 3.11 or later and `uv`.

git clone --recurse-submodules https://github.com/gatepost-dev/js
pnpm install
pnpm exec playwright install chromium
pnpm check

The tests of the field and of the React package run in Chromium, which Playwright installs once.
`pnpm check` runs the journeys of the field too, with a test page on port 3000 and the mock server
on port 4010, so both ports must be free.

`pnpm check` also builds the packages. Then it runs publint, attw, size-limit and API Extractor on
the result, and it starts each built package once. It checks the licence of each dependency and of
each file, and it checks the workflows with zizmor. It starts REUSE and zizmor with `uvx` at pinned
Expand Down
3 changes: 3 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default defineConfig(
'**/.stryker-tmp/**',
'spec/**',
'**/spec-data.ts',
'**/spec-messages.ts',
],
},
eslint.configs.recommended,
Expand Down Expand Up @@ -90,6 +91,8 @@ export default defineConfig(
},
],
'jsdoc/tag-lines': ['error', 'any', { startLines: 1 }],
// A getter documents a property, so a @returns tag would only repeat its summary.
'jsdoc/require-returns': ['error', { checkGetters: false }],
'jsdoc/check-tag-names': [
'error',
{ definedTags: ['packageDocumentation', 'internal', 'defaultValue'] },
Expand Down
14 changes: 11 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
"scripts": {
"check": "run-s check:source check:build check:policy",
"check:source": "run-s generate:check format:check lint typecheck test",
"check:build": "run-s build package:check",
"check:build": "run-s build package:check test:browser",
"check:policy": "run-s tells context:check check:licences workflows:check",
"check:licences": "run-s licences:check licence-files:check reuse:check",
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "eslint . --max-warnings 0",
"typecheck": "pnpm -r run typecheck",
"test": "pnpm -r run test && node --test scripts/*.test.mjs",
"test:browser": "pnpm -r run test:browser",
"build": "pnpm -r run build",
"package:check": "pnpm -r run package:check",
"tells": "python3 spec/scripts/check-tells",
Expand All @@ -25,27 +26,34 @@
"licence-files:check": "pnpm -r run licence-files:check",
"reuse:check": "uvx --with charset-normalizer==3.5.2 reuse@6.2.0 lint",
"workflows:check": "uvx zizmor@1.30.1 --offline .github/workflows",
"generate": "node scripts/generate-spec-data.mjs",
"generate:check": "node scripts/generate-spec-data.mjs --check"
"generate": "node scripts/generate-spec-data.mjs && node scripts/generate-messages.mjs",
"generate:check": "run-s generate:check:*",
"generate:check:data": "node scripts/generate-spec-data.mjs --check",
"generate:check:messages": "node scripts/generate-messages.mjs --check"
},
"packageManager": "pnpm@12.8.1",
"devDependencies": {
"@arethetypeswrong/cli": "^0.18.5",
"@axe-core/playwright": "^4.13.0",
"@changesets/cli": "^3.0.3",
"@commitlint/cli": "^21.2.3",
"@commitlint/config-conventional": "^21.2.3",
"@eslint-community/eslint-plugin-eslint-comments": "^4.8.1",
"@eslint/js": "^10.0.1",
"@microsoft/api-extractor": "^7.59.3",
"@playwright/test": "^1.63.0",
"@size-limit/preset-small-lib": "^14.1.0",
"@stryker-mutator/core": "^10.0.0",
"@types/node": "^22.20.4",
"@vitest/browser-playwright": "^5.0.3",
"@vitest/coverage-v8": "^5.0.3",
"ajv": "^8.20.0",
"axe-core": "^4.13.0",
"eslint": "^10.11.0",
"eslint-plugin-jsdoc": "^65.0.1",
"fast-check": "^4.10.2",
"npm-run-all2": "^9.0.3",
"playwright": "^1.63.0",
"prettier": "^3.9.9",
"publint": "^0.3.24",
"size-limit": "^14.1.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ The client also takes a `transport` option, a function like `fetch`, and each ca
| ------------- | ---------------------------------------------- |
| Node | 22 or later, or a current browser with `fetch` |
| Module format | ESM only |
| Gatepost spec | 0.2.0 |
| Gatepost spec | 0.3.0 |

## Docs

Expand Down
2 changes: 1 addition & 1 deletion packages/client/etc/client.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,6 @@ export interface ReverseUnit {
}

// @public
export const SPEC_VERSION = "0.2.0";
export const SPEC_VERSION = "0.3.0";

```
4 changes: 2 additions & 2 deletions packages/client/src/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* ```ts
* import { SPEC_VERSION } from '@gatepost/client';
*
* SPEC_VERSION; // '0.2.0'
* SPEC_VERSION; // '0.3.0'
* ```
*/
export const SPEC_VERSION = '0.2.0';
export const SPEC_VERSION = '0.3.0';
2 changes: 1 addition & 1 deletion packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ The package checks the form of a postcode. It cannot tell whether a building has
| ------------- | ----------------------------------- |
| Node | 22 or later, or any current browser |
| Module format | ESM only |
| Gatepost spec | 0.2.0 |
| Gatepost spec | 0.3.0 |

## Docs

Expand Down
2 changes: 1 addition & 1 deletion packages/core/etc/core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export interface Segments {
}

// @public
export const SPEC_VERSION = "0.2.0";
export const SPEC_VERSION = "0.3.0";

// @public
export function stateName(stateCode: string): string | null;
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* ```ts
* import { SPEC_VERSION } from '@gatepost/core';
*
* SPEC_VERSION; // '0.2.0'
* SPEC_VERSION; // '0.3.0'
* ```
*/
export const SPEC_VERSION = '0.2.0';
export const SPEC_VERSION = '0.3.0';
Loading