You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
AI: Powered Templates (generate a list from a description) #14
feature: "powered_template" drafts a complete list schema plus starter rows from a plain-language description (e.g. "a reading list with title, author, status, and rating").
On the web this is a Powered Templates tab in the new-list flow, shown only to subscribers.
Live-verified corrections from the service layer (#9 / PR #137) — read before building this
Five things contradict what the AI epic and these issues assume.
1. quota is not the same object on both endpoints. GET /api/ai/status → {usedToday, dailyLimit, remaining}. POST /api/ai/suggest → {usedToday, dailyLimit} with no remaining (verified twice). So AiQuota.Remaining is int?, and a post-suggest quota chip cannot read .Remaining — bind RemainingOrComputed instead. #10's "remaining quota is visible wherever an AI action is offered" has to account for that.
2. There is no provider picker to build.
AI is Anthropic-only, app-wide; per-user provider keys were removed server-side 2026-09-05. The provider field on /generate is audit-ledger-only and does not select anything, and the openai/gemini entries in defaultModels are dormant and unreachable. Don't surface a provider choice.
3. crossPost has six fields, not two — crossPostToBluesky, selectedMastodonIds, crossPostToLinkedIn, selectedLinkedInTargets, crossPostToTwitter, linkedInLinkAsFirstComment. Both crossPost and scheduleImmediately are message_series-only (relevant to #12).
4. A sixth 422 code exists that the epic didn't list: refused. Treat it like invalid_ai_output. Modelled and marked retryable.
5. Quota accounting is asymmetric, and it shapes the UI.
Input-validation 422s cost nothing (usedToday stayed 0 across two), but any call that reaches the model costs a unit — including failures. So client-side pre-flight is load-bearing, not politeness: enforce the word caps, the 10-word series gate (#12/#13), a missing powered_document source ref (#15), and the non-persistable-artifact rule (#11) before calling. And no auto-retry — a retry burns another unit.
Also: context.spacingMinutes is accepted but ignored (series rows always land 4 minutes apart), so it's deliberately not exposed.
Note on the exception type
The code is surfaced on a new AiApiException, not on InterlinedApiException — the latter is sealed and was being rewritten in #130. Folding them together is flagged as a follow-up in the code. Catch AiApiException in these UI issues.
Unverified
POST /api/ai/generate was never called (it persists content to a shared test account). Its envelope is contract-transcribed, flagged unverified in doc comments, and keeps the raw created element for read-after-write. Whichever of #12–#15 lands first should verify it against a real account and tighten the model.
Implemented in #174 — work continues in the PR from here.
PoweredTemplatePanel is a self-contained card gated on #10's IsAiAvailable, hosted with a single line in ListsView.xaml and no code-behind edit (the #55/#56 pattern, since that file is contended by #161/#163/#164). The preview renders every proposed column as an editable row — label, type picker, required toggle, remove — alongside the starter rows as a real table with editable cells, add/remove row, and add column with keys slugified into the documented ^[a-z][a-z0-9_-]*$ shape.
One /suggest call pinned the list artifact for the first time, and it changed the design: the DSL came back carrying options, defaultValue and a visibility.condition that nothing in this repo knew about. So the column editor is a deliberately partial projection that round-trips every unparsed member byte-for-byte, verified by a harness that replays the real response, edits it, and asserts those members survive — along with untouched cells keeping their original JSON kind rather than being re-typed from their string rendering.
POST /api/ai/generate was not called (it writes to a shared account), so the confirm path re-fetches with GET /api/lists/{id} and treats the returned {listId} as a hint; that field name is the one thing left to confirm against a real account.
Activity
Adron commentedon Sep 16, 2026
Live-verified corrections from the service layer (#9 / PR #137) — read before building this
Five things contradict what the AI epic and these issues assume.
1.
quotais not the same object on both endpoints.GET /api/ai/status→{usedToday, dailyLimit, remaining}.POST /api/ai/suggest→{usedToday, dailyLimit}with noremaining(verified twice). SoAiQuota.Remainingisint?, and a post-suggest quota chip cannot read.Remaining— bindRemainingOrComputedinstead. #10's "remaining quota is visible wherever an AI action is offered" has to account for that.2. There is no provider picker to build.
AI is Anthropic-only, app-wide; per-user provider keys were removed server-side 2026-09-05. The
providerfield on/generateis audit-ledger-only and does not select anything, and theopenai/geminientries indefaultModelsare dormant and unreachable. Don't surface a provider choice.3.
crossPosthas six fields, not two —crossPostToBluesky,selectedMastodonIds,crossPostToLinkedIn,selectedLinkedInTargets,crossPostToTwitter,linkedInLinkAsFirstComment. BothcrossPostandscheduleImmediatelyaremessage_series-only (relevant to #12).4. A sixth
422code exists that the epic didn't list:refused. Treat it likeinvalid_ai_output. Modelled and marked retryable.5. Quota accounting is asymmetric, and it shapes the UI.
Input-validation
422s cost nothing (usedTodaystayed 0 across two), but any call that reaches the model costs a unit — including failures. So client-side pre-flight is load-bearing, not politeness: enforce the word caps, the 10-word series gate (#12/#13), a missingpowered_documentsource ref (#15), and the non-persistable-artifact rule (#11) before calling. And no auto-retry — a retry burns another unit.Also:
context.spacingMinutesis accepted but ignored (series rows always land 4 minutes apart), so it's deliberately not exposed.Note on the exception type
The
codeis surfaced on a newAiApiException, not onInterlinedApiException— the latter issealedand was being rewritten in #130. Folding them together is flagged as a follow-up in the code. CatchAiApiExceptionin these UI issues.Unverified
POST /api/ai/generatewas never called (it persists content to a shared test account). Its envelope is contract-transcribed, flagged unverified in doc comments, and keeps the rawcreatedelement for read-after-write. Whichever of #12–#15 lands first should verify it against a real account and tighten the model.Adron commentedon Sep 16, 2026
Implemented in #174 — work continues in the PR from here.
PoweredTemplatePanelis a self-contained card gated on #10'sIsAiAvailable, hosted with a single line inListsView.xamland no code-behind edit (the #55/#56 pattern, since that file is contended by #161/#163/#164). The preview renders every proposed column as an editable row — label, type picker, required toggle, remove — alongside the starter rows as a real table with editable cells, add/remove row, and add column with keys slugified into the documented^[a-z][a-z0-9_-]*$shape.One
/suggestcall pinned thelistartifact for the first time, and it changed the design: the DSL came back carryingoptions,defaultValueand avisibility.conditionthat nothing in this repo knew about. So the column editor is a deliberately partial projection that round-trips every unparsed member byte-for-byte, verified by a harness that replays the real response, edits it, and asserts those members survive — along with untouched cells keeping their original JSON kind rather than being re-typed from their string rendering.POST /api/ai/generatewas not called (it writes to a shared account), so the confirm path re-fetches withGET /api/lists/{id}and treats the returned{listId}as a hint; that field name is the one thing left to confirm against a real account.