Skip to content

AI: Powered Templates (generate a list from a description) #14

Closed

Description

@Adron

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.

  • /suggest → kind: "list" artifact (schema + starter rows).
  • /generate → {listId}.

Depends on

The schema/columns work in the List Schema (DSL) epic — the preview needs to render and edit real column definitions, not a JSON blob.

Acceptance criteria

  • A "Powered Template" option in the new-list flow in Views/ListsView.xaml, gated on IsAiAvailable.
  • Preview renders the proposed columns (type, label, required) and the starter rows in a table.
  • Columns and rows are editable before confirming.
  • Confirm calls /api/ai/generate, then the list opens from a fresh GET.

Activity

  1. added
    parityWeb/API feature-parity work
    P1Table stakes
    on Sep 15, 2026
  2. Adron commented on Sep 16, 2026

    @Adron
    MemberAuthor

    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.

  3. Adron commented on Sep 16, 2026

    @Adron
    MemberAuthor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Table stakesarea:aiArea: aiparityWeb/API feature-parity work

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions