Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
26 changes: 26 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Agent instructions for lameta

## Issue tracker: none

lameta uses **no card tracker**. There is no YouTrack project, and GitHub issues are not
used as work cards for branches.

- Ticket ids: none. Branch names carry no ticket id.
- Tracker skill: none.

Skills that want a tracker (`preflight`, `pr-ready-for-human`, `add-test-ideas`) must skip
every card step, note the skip once, and continue. Do not ask again, and do not search for a
tracker. A file name such as `playwright-screenshot-LAM-25.png` is a stray artifact, not
evidence of a tracker.

## Toolchain

Yarn, one TypeScript stack, no .NET.

- Typecheck: `yarn tsc --noEmit`
- Lint: `yarn eslint src`
- Unit tests: `yarn vitest run`
- Build: `yarn build`
- End-to-end tests: `yarn build` first, then `yarn e2e`. The suite drives `dist/`, and
`playwright.config.ts` runs no build of its own, so a source edit without a build tests the
old bundle.
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
20 changes: 20 additions & 0 deletions PAPERCUTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Papercuts

Short notes about friction and traps in this repo. Append; do not rewrite.

## An e2e test that reads text cannot see a clipped dropdown (2026-08-26)

`e2e/createLanguage.e2e.ts` asserted on the text of the react-select menu. The menu was in
the document, so the tests passed, but on the Session and Person forms an ancestor with
`overflow-y: hidden` cut off all 300 pixels of it, so the user saw nothing. Playwright's
`:visible` does not help: it tests the bounding box, not clipping by an ancestor.

For a dropdown, a tooltip, or any element that leaves its own box, assert the geometry as
well as the text. `expectNothingClipsTheMenu` in that file walks up from the menu and fails
if an ancestor with overflow hidden ends above the menu's bottom.

## The e2e suite runs `dist/`, not the source (2026-08-26)

`playwright.config.ts` has no build step, so `npx playwright test` drives whatever
`yarn build` last wrote. A source edit has no effect on an e2e run until you build. Cost: two
test runs that measured the old bundle and reported the defect as fixed.
9 changes: 8 additions & 1 deletion archive-configurations/extractFromJSON.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,16 @@ function createPoFilesFromOneSetOfJsons(set: string) {
console.log(
`extractFromJSON: Gathered ${messages.length} strings from ${set}.`
);
// A PO string ends at the first unescaped quote, so a description holding a quote, e.g.
// `New speakers of Edolo`, truncates the entry and leaves stray tokens after it. Escape the
// backslash first, then the quote, or the escape gets escaped a second time.
const escapeForPo = (text: string) =>
text.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
const content = messages
.map((m) => {
return `msgctxt "${m.context}"\nmsgid "${m.message}"\nmsgstr ""\n`;
return `msgctxt "${escapeForPo(m.context)}"\nmsgid "${escapeForPo(
m.message
)}"\nmsgstr ""\n`;
})
.join("\n");
//console.log(content);
Expand Down
32 changes: 32 additions & 0 deletions archive-configurations/poEscaping.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, it, expect } from "vitest";
import * as fs from "fs";
import * as path from "path";

// extractFromJSON.ts writes these catalogs from the json5 configuration files. A PO string
// ends at the first unescaped quote, so a field description holding a quote used to truncate
// the entry and leave stray tokens after it. See escapeForPo in extractFromJSON.ts.
const generatedCatalogs = ["fields.po", "vocabularies.po"];

// A well-formed PO string: an opening quote, then any run of characters in which every quote
// and every backslash is escaped, then the closing quote.
const wellFormedPoString = /^(msgid|msgctxt|msgstr) "(?:[^"\\]|\\.)*"$/;

describe("the generated PO catalogs", () => {
generatedCatalogs.forEach((catalog) => {
it(`${catalog} escapes every quote`, () => {
const text = fs.readFileSync(
path.join("locale", "en", catalog),
"utf-8"
);
const badLines = text
.split(/\r?\n/)
.map((line, index) => ({ line: line.trim(), number: index + 1 }))
.filter(
(l) =>
/^(msgid|msgctxt|msgstr) "/.test(l.line) &&
!wellFormedPoString.test(l.line)
);
expect(badLines).toEqual([]);
});
});
});
Loading
Loading