OutreachAnytypeFix #2208
PR #2233 MERGED · 4h turnaround UX / deterministic preview +35 / -1 LOC @ra3orblade clean accept · 0 review comments

Anytype

anyproto/anytype-ts · issue #2208 · PR #2233 · branch fix/2208-date-format-picker-symmetric-dates

The settings *Date Format* picker rendered every preview by formatting now(). On 12 days a year where day equals month (Jan 1, Feb 2, …, Dec 12) the Short (d/m/Y) and ShortUS (m/d/Y) options produce the exact same string, `05.05.2026` for both, so the user can't tell which radio button is which. Five-line fix: pin the preview sample to a fixed asymmetric date (Jul 30, 2020, already the convention used in the DateFormat enum comments). Plus two regression-guard tests in `date.test.ts`.

5 / 0
code LOC in menu.ts (+ explanatory comment)
2 / 2
new regression-guard tests passing
12 / 365
days the bug manifests per year
7.9k ★
anyproto/anytype-ts · E2EE local-first knowledge graph
01 · The problem

Two radio buttons, identical labels

Open Settings → Language & Region → Date Format on any day where day equals month (12 days a year), and the picker shows two options with the exact same text. The reporter's screenshots from May 5 (`05.05.2026`) show two radio buttons reading `05.05.2026` and `05.05.2026` with no way to distinguish them, one is `dd.mm.yyyy`, the other is `mm.dd.yyyy`, and picking the wrong one changes how every date in the app renders.

The original code

// src/ts/lib/util/menu.ts dateFormatOptions () { return ([ { id: I.DateFormat.Default }, { id: I.DateFormat.MonthAbbrBeforeDay }, { id: I.DateFormat.MonthAbbrAfterDay }, { id: I.DateFormat.Short }, // "d/m/Y" → 05.05.2026 on May 5 { id: I.DateFormat.ShortUS }, // "m/d/Y" → 05.05.2026 on May 5 ← same string { id: I.DateFormat.ISO }, { id: I.DateFormat.Long }, { id: I.DateFormat.Nordic }, { id: I.DateFormat.European }, ] as { id: I.DateFormat; name: string }[]).map(it => { it.name = U.Date.dateWithFormat(it.id, U.Date.now()); // ← root cause: now() return it; }); };

Every option label is built by formatting today's timestamp. On symmetric dates `dd == mm`, the two single-digit-padded slash formats collapse to the same string.

02 · The fix

Pin the preview to a fixed asymmetric date

The DateFormat enum already documents its own canonical examples using Jul 30, 2020 (`Short = "30/07/2020"`, `ShortUS = "07/30/2020"`), chosen precisely because day ≠ month. The fix uses that same sample as the picker preview, so every option renders distinguishably year-round and the labels never depend on what day it is.

Diff

dateFormatOptions () { + // Use a fixed, asymmetric sample date (Jul 30, 2020) so Short (30/07/2020) + // and ShortUS (07/30/2020) stay visually distinct year-round. Using today's + // date collapsed both labels to the same string on symmetric dates + // (01/01, 02/02, ..., 12/12), see issue #2208. + const sample = U.Date.timestamp(2020, 7, 30); + return ([ { id: I.DateFormat.Default }, ... ] as { id: I.DateFormat; name: string }[]).map(it => { - it.name = U.Date.dateWithFormat(it.id, U.Date.now()); + it.name = U.Date.dateWithFormat(it.id, sample); return it; }); };

Two regression-guard tests

Added to src/ts/lib/util/date.test.ts, both pass locally under vitest.

describe('dateWithFormat (picker sample distinguishability)', () => { // Positive guard: the asymmetric sample produces distinct labels. it('Short and ShortUS produce distinguishable labels at an asymmetric sample (Jul 30, 2020)', () => { const sample = UtilDate.timestamp(2020, 7, 30); expect(UtilDate.dateWithFormat(I.DateFormat.Short, sample)) .not.toBe(UtilDate.dateWithFormat(I.DateFormat.ShortUS, sample)); }); // Negative documentation: the bug, Short == ShortUS on May 5, pinned so that // any future regression picking today's date would fail the positive test above. it('Short and ShortUS collapse on a symmetric date (the bug reported in #2208)', () => { const symmetric = UtilDate.timestamp(2026, 5, 5); expect(UtilDate.dateWithFormat(I.DateFormat.Short, symmetric)) .toBe(UtilDate.dateWithFormat(I.DateFormat.ShortUS, symmetric)); }); });
03 · The outreach

Where this stands in the conversation funnel

PR open against develop (Anytype's default branch), GitHub-noreply identity from the start, vitest + tsc + biome all clean on the patched files.

Done

Investigation + tests

Traced the picker to UtilMenu.dateFormatOptions, confirmed both Short (`d/m/Y`) and ShortUS (`m/d/Y`) use zero-padded numeric formats that collapse when day == month. Added two regression-guard tests covering positive (asymmetric sample → distinguishable) and negative (symmetric date → bug reproduced) cases, both pass locally.

Done

Surgical patch + clean toolchain

Five lines of code change in menu.ts (one helper-comment block + one line move), 28 lines of new test coverage. TS clean (3 pre-existing errors in `dispatcher.ts` / `relation.ts` due to ungenerated proto files, unrelated). Biome lint clean on both patched files. Vitest run on the new tests: 2 passed.

Done

PR open with reporter cross-link

PR body explains the root cause with an inline diff, lists the test commands, and includes the AI-assistance disclosure. Issue #2208 cross-commented so the reporter sees the fix without navigating to the PR list.

Done

MERGED, 4-hour turnaround, zero review friction

2026-05-27 21:43 CEST: @ra3orblade (Andrew Simachev, the area owner identified in Phase 4b) approved → merged into develop → closed #2208 as completed, all in 4 consecutive minutes. No review comments requested. Clean accept. Maintainer signal validated retroactively, the +35/-1 footprint, the deterministic test pair, and the alignment with the enum-comment convention (Jul 30, 2020 sample) all combined into a one-look merge.

Goal

Paid follow-up conversation via the E2EE / local-first stack

The shipped fix is the proof. The follow-up, once it makes sense to send, given Anytype is a small team, rides the K-Perception stack overlap (closed-beta encrypted notes app with AES-256-GCM + Y.js CRDT collab on encrypted blobs), the same problem space Anytype solves at a much larger scale. Paid-conversation odds rise because the merge happened without any sales surface attached, which means the next outreach lands with track record rather than pitch.

04 · How to verify

Reproduce every claim on this page

# 1. Pull the branch from the fork git clone https://github.com/999purple999/anytype-ts --branch fix/2208-date-format-picker-symmetric-dates # 2. Inspect the change, fully contained in one file git diff develop -- src/ts/lib/util/menu.ts src/ts/lib/util/date.test.ts # 3. Run the new tests node node_modules/vitest/dist/cli.js run src/ts/lib/util/date.test.ts -t "picker sample" # expect: 2 passed # 4. Manual verification: # - set system clock to May 5 (any year) # - open Settings → Language & Region → Date Format # - BEFORE the patch: two radio buttons read "05.05.2026" identically # - AFTER the patch: every option preview is distinguishable