Skip to content

feat(lists): creation options the API accepts (parentId, folderId, messageId, initialRows, metadata, source) + parent breadcrumb (#54) - #98

Merged
Adron merged 1 commit into
parity/queuefrom
issue/54-list-creation-options
Sep 16, 2026
Merged

Adron merged 1 commit into
parity/queuefrom
issue/54-list-creation-options

Conversation

@Adron

@Adron Adron commented Sep 16, 2026

Copy link
Copy Markdown
Member

Closes #54. Part of epic #49. Groundwork for #8 (Powered Templates) and the #10 materialize epic.

What changed

Request layer. CreateListRequest gains parentId, folderId, messageId, initialRows,
metadata and source. All default to null and, with encodeDefaults = false +
explicitNulls = false, are omitted rather than sent as null — existing callers produce
byte-identical request bodies.

One correction worth calling out: schema was typed String?. The help centre's Lists
reference is explicit that POST /api/lists takes the DSL as an object, "not a
comma-separated string" — the string form belongs to PUT /api/lists/{id}/schema, which is
untouched. It is now JsonObject?. No caller ever set it, so nothing changes behaviourally.

Repository. createList(...) takes all six options. initialRows goes through the same
toJsonData() the row-write path uses, so a starter row serialises exactly like
POST /api/lists/{id}/data's data. Adds createListFromMessage(...) and getParentChain(...).

parentId is user-visible, not just wire support. ListDto models parentId, the recursive
nested parent, and children; ListSummary/CachedListEntity carry parentId (feature Room DB
→ v2, destructive fallback already configured). getParentChain walks up, consuming the server's
inlined parent when present and fetching the level otherwise, with cycle and depth-10 guards;
a level that fails truncates the breadcrumb rather than failing the screen. ListDetailScreen
renders a tappable root-first breadcrumb plus a "New child list" action.

messageId gets request support and a createListFromMessage method only — no entry point in
:feature:messages
, which #12/#13 own.

source is a ListSource enum (local, github) rather than a raw string.

Verification

./gradlew :app:assembleDebug testDebugUnitTest → BUILD SUCCESSFUL, all modules green.
:feature:lists 131 tests, 0 failures (was 110): new CreateListRequestTest (5),
DefaultListsRepositoryCreationTest (10), and 5 ListDetailViewModelTest cases covering
breadcrumb resolution, absence, failure and child creation. 3 Compose cases compile, not executed
(no emulator).

TDD note: the cycle-guard test genuinely failed first — 3 requests instead of 2, because the guard
ran after the fetch — which drove checking visited before spending a request.

Open questions for the owner — please weigh in

  1. From Template tab: deliberately not built. There is no list-template endpoint anywhere. The
    spec has only /api/documents/templates, /api/documents/templates/seed-defaults and
    /api/documents/from-template; the help centre has no list-template page. Where do the web's
    list templates come from?
  2. initialRows shape is unverified. The generated spec types it "type": "string", and the
    human-written reference never mentions it. Modelled as an array of flat column-keyed objects and
    documented in KDoc. Worth confirming before AI: Powered Templates tab on list creation #8 relies on it.
  3. folderId is not a documented create field. It is a real list column, but appears on
    neither the OpenAPI POST /api/lists schema nor the help reference's create body — folder
    placement is documented on PUT /api/lists/{id} (with null = root). Included as the issue
    asked, with a KDoc note. No caller passes it today.
  4. Parent nesting depth is unknown and could not be observed. GET /api/lists carries
    parent/children, but the test account has exactly one list, with no parent and no children,
    so whether GET /api/lists/{id} nests more than one level is unobservable without creating data
    on a live account. The walk handles both cases and is tested both ways, so this is safe
    either way.

Reviewer notes

  • Scope exception: two lines in InterlinedListNavHost.kt wiring onOpenList on
    ListDetailRoute. Without it the breadcrumb and child-list navigation are dead. The parameter has
    a no-op default, so dropping the line still compiles.
  • Not done, worth a follow-up: PUT /api/lists/{id} also accepts parentId, so re-parenting an
    existing list
    is still unsupported.

POST /api/lists takes more than title/description/schema/isPublic.
CreateListRequest now carries parentId, folderId, messageId, initialRows,
metadata and source, each omitted from the body when null so existing
callers send exactly the request they always sent, and all of them are
plumbed through ListsRepository.createList. initialRows go out in the same
flat, column-keyed shape POST /api/lists/{id}/data uses; metadata stays a
JsonObject so arbitrary keys round-trip untouched; source is modelled as a
ListSource enum ("local"/"github") rather than a raw string.

parentId is reachable from the UI: the detail screen offers "New child list"
and renders a breadcrumb of the list's ancestors, resolved by walking the
parent chain — consuming the nested `parent` object when the server inlines
it, fetching the level otherwise, with cycle and depth guards. A level that
fails to load truncates the breadcrumb instead of failing the screen. Lists
now cache their parentId, so the feature's Room schema moves to v2, and the
nav host hands the detail route one onOpenList callback that both breadcrumb
hops and newly created child lists use.

messageId gets request-layer support plus a createListFromMessage repository
call only; the affordance on a message belongs to the cross-object work in
:feature:messages.

Closes #54
@Adron
Adron merged commit 3825402 into parity/queue Sep 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant