Milestone v1.0: FamilySync MVP #1

Merged
luckberg merged 376 commits from gsd/v1.0-milestone into main 2026-06-10 17:39:19 -04:00
2 changed files with 14 additions and 1 deletions
Showing only changes of commit 9b860617c5 - Show all commits
@@ -0,0 +1 @@
ALTER TABLE `list_items` MODIFY COLUMN `rank` varchar(255) COLLATE utf8mb4_bin NOT NULL;
+13 -1
View File
@@ -10,8 +10,20 @@ import {
boolean,
index,
unique,
customType,
} from 'drizzle-orm/mysql-core'
// Custom varchar type with explicit binary collation.
// Drizzle 0.45.x does not expose a first-class collation option on varchar,
// so we use customType to emit `varchar(255) COLLATE utf8mb4_bin`.
// utf8mb4_bin is required for fractional-indexing rank keys: uppercase-prefixed
// ranks (e.g. 'Zz') must sort BEFORE lowercase ranks (e.g. 'a0') — matching JS
// string order — so that drag-to-top persists across a DB ORDER BY rank.
const varcharBin = (name: string) =>
customType<{ data: string; driverData: string }>({
dataType: () => 'varchar(255) COLLATE utf8mb4_bin',
})(name)
// ── Phase 4: List tables ───────────────────────────────────────────────────
// Imported by test/setup.ts for afterEach cleanup — keep exports consistent.
@@ -228,7 +240,7 @@ export const listItems = mysqlTable(
.references(() => lists.id, { onDelete: 'cascade' }),
text: varchar('text', { length: 500 }).notNull(),
checked: boolean('checked').default(false).notNull(),
rank: varchar('rank', { length: 255 }).notNull(), // fractional-indexing string (D-13)
rank: varcharBin('rank').notNull(), // fractional-indexing string (D-13) — COLLATE utf8mb4_bin
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().onUpdateNow(),
},