Skip to content

Conversation

ronjalh
Copy link
Member

@ronjalh ronjalh commented Oct 15, 2026 •

Summary

Adds the fields that the database has and schema.graphql did not expose, and describes them in docs/API.md.

  • A gymnast's list: Gymnast.elements(apparatusId) with addGymnastElement and removeGymnastElement. Both return the whole Gymnast, so the client can update its cache.
  • Routine timestamps: Routine.createdAt and updatedAt. setRoutineItems must touch updatedAt in the same transaction as the items.
  • Removed items: CatalogItem.removed: Boolean! (also on Element and Vault).

Decisions

What "removed" means. An element or vault is removed when it is no longer in the edition of the Code of Points that the app uses. The database sets removedAt and never deletes the row, because routines and lists point to it. It is global catalog data set by the seed script, never by a coach. No removed items exist today, since data/ only has the 2025–2028 edition.

Errors are never silent. Adding an item that is already in the list throws ALREADY_EXISTS, and removing one that is not in the list throws NOT_FOUND. The list is unchanged in both cases, so repeating a call does no harm, but the client is told that nothing changed. The names follow the gRPC/Google error vocabulary. The UI should disable the button while a call runs and treat these two errors as "my list is out of date, reload".

Removed items:

  • not found by searchCatalog, but returned by element(id), vault(id) and catalogItem(id) with removed: true
  • cannot be added to a routine or a list (INVALID_ITEMS), but can stay where they are and can be removed
  • do not count in Routine.dScore, and calculateDScore rejects them with INVALID_ITEMS
  • the UI shows a persistent inline warning with the number of items that no longer count and a button to remove them (not a toast)
  • the seed script marks items as removed, never changes user data, and prints how many routines and lists use each one

Limit: a gymnast's list has at most 100 items (LIMIT_REACHED).

Two tabs: setRoutineItems replaces the whole list and the last save wins. This is written down as a choice for the prototype, with CONFLICT and updatedAt as the way to change it later.

Merged with dev

dev got its own version of the gymnast list and the removed flag through #38 (Gymnast.elements, addGymnastElement, removeGymnastElement, where adding twice or removing a missing item did nothing and there was no limit). This PR keeps dev's names and additions (difficultyValues, no difficultyLetter, the same element twice in a routine, removed variants left out of variants) and keeps the decisions for #37: adding twice gives ALREADY_EXISTS, removing a missing item gives NOT_FOUND, a gymnast's list has at most 100 elements, names are 1–70 characters, and removed items do not count in the D-score. The part of dev's docs that said the opposite is changed.

Other docs

  • docs/API.md: error table with the new code and the Apollo codes (GRAPHQL_VALIDATION_FAILED, INTERNAL_SERVER_ERROR), UNAUTHENTICATED also for an expired session or Google token, limits and tests.
  • docs/USER_LIST_PAGINATION.md and docs/AI_USAGE.md are updated.

Notes for review

  • The schema parses with graphql@16 (buildSchema). Nothing else can be run, because there is no server yet.
  • 22 create the database schema #38 also edits schema.graphql and docs/API.md, in other places. I expect no conflict, but check when merging.
  • Names: gymnast and routine names are trimmed and must be 1–70 characters (BAD_USER_INPUT), the length the UK Government Data Standards give for a full name in one field. Checked in the backend; the database does not check it yet.

Closes #37

🤖 Generated with Claude Code

Ronja added 2 commits October 15, 2026 14:48
…he GraphQL schema

Gymnast.items with addGymnastItem and removeGymnastItem, Routine.createdAt and updatedAt, and CatalogItem.removed. API.md describes the rules, errors and limits.
…d items do not count

addGymnastItem throws ALREADY_EXISTS and removeGymnastItem throws NOT_FOUND when nothing would change. A gymnast's list is limited to 100 items. Removed items do not count in the D-score, and calculateDScore rejects them. API.md describes the warning in the UI, the seed report and the other error codes.
@ronjalh ronjalh requested a review from a team as a code owner October 15, 2026 13:10
Names are trimmed and checked in the backend, BAD_USER_INPUT otherwise.
@anettkva
Copy link
Member

Looks good:) Im ready to merge when you're done with fixing the merge conflicts @ronjalh

@ronjalh ronjalh self-assigned this Oct 16, 2026
Resolves the conflicts with the search and schema changes in dev. dev's names are kept (Gymnast.elements, addGymnastElement, removeGymnastElement, difficultyValues, no difficultyLetter, duplicates in a routine). The decisions for #37 are kept: errors for a duplicate add and a missing remove, a limit of 100, names of 70 characters, and removed items do not count.
@ronjalh
Copy link
Member Author

ronjalh commented Oct 16, 2026

Do you agree with this approach?

While this PR was open, #38 put its own version of the gymnast's list in dev (Gymnast.elements, addGymnastElement, removeGymnastElement). This PR now builds on it, keeps its names, and changes some of the rules. I want to hear from the team before it is merged, because it changes what dev says today.

What changes compared with dev

  1. Errors instead of silent no-ops. Adding an element that is already in the list gives ALREADY_EXISTS, and removing one that is not in the list gives NOT_FOUND. In dev both did nothing and gave no answer. The reason: a call that silently changes nothing hides that the client's list is out of date. The state is the same either way (a repeated call does no harm), but the client is told that nothing changed. The cost is in the UI: it should disable the button while a call runs, and treat these two errors as "my list is old, reload it" and not show an error.
  2. A limit of 100 elements in a gymnast's list (LIMIT_REACHED). In dev there was no limit.
  3. Names of gymnasts and routines are trimmed and must be 1–70 characters (BAD_USER_INPUT).
  4. Removed items do not count in the D-score, and calculateDScore rejects them with INVALID_ITEMS. A removed item cannot be added to a routine or a list, but it can stay where it is and can be removed.

Kept from dev: the names Gymnast.elements, addGymnastElement and removeGymnastElement, difficultyValues, no difficultyLetter, the same element twice in a routine, and removed variants left out of variants.

Alternatives

  • (a) Keep the silent no-op from 22 create the database schema #38.
  • (b) No errors, but the mutation returns a flag such as added: Boolean, so the client can see what happened without handling an error.

@anettkva, you wrote the version in #38 and will probably write the resolvers, so I would like to hear from you before this merges. The same goes for everyone who builds the UI (#39–#48), since the UI has to handle ALREADY_EXISTS and NOT_FOUND.

Please answer with: agree, object, or prefer (a) or (b).

Comment made with Claude Code in discussion with Ronja

Keeps both AI_USAGE sections. Puts the one-line descriptions in schema.graphql back as they are in dev: an earlier Prettier run had turned all of them into blocks.
@anettkva
Copy link
Member

anettkva commented Oct 16, 2026 •

Agree with most of it, but I prefer (a) for the gymnast's list.

Agree: the limit of 100, names trimmed to 1–70 characters, and removed items cannot be added but can stay or be removed.

Prefer (a), keep the silent no-op:

  • Both mutations return Gymnast!, so the client already gets the up-to-date list.
  • NOT_FOUND in removeGymnastElement would mean both "no such gymnast" and "not in the list", and the client cannot tell them apart.
  • A double click or a retry would give an error even though the result is right, and Create a navbar #39–Add element variants list to element page #48 would all have to handle it.

Removed items: I agree that calculateDScore rejects them. The user should get a warning that the element is outdated and remove it to get a score. But Routine.dScore still leaves removed items out, so a saved routine shows a score that the calculator would refuse. Could Routine.dScore become Float (nullable) and be null while the routine has a removed item? Then the rule is the same everywhere.

Comment made with Claude Code in discussion with Anette

@anettkva
Copy link
Member

Not ready to merge yet. The merge of dev into this branch (21506ae) broke schema.graphql.

Must fix before merging

  1. Five fields ended up inside doc comments, so they are missing from the schema: Vault.dValue (line 127), CatalogFilter.difficultyValues (165), createGymnast (303), renameGymnast (305) and createRoutine (326). Each one needs its own line after the closing """. This is what @xeniag quote comments point at.
  2. The merge reverted the search comment from dev. searchCatalog(search:) should again mention the limit of 80 characters and 8 words, and that text without searchable words finds nothing.
  3. Run Prettier on the file. It fails now, and the comments mix one-line and multi-line """.

calculateDScore(apparatusId, ...): I'd skip it. Each item already has its apparatus, items from different apparatus already give INVALID_ITEMS, and an empty list scores 0 on every apparatus.
@ronjalh

Ronja added 3 commits October 17, 2026 09:00
Vault.dValue, CatalogFilter.difficultyValues, createGymnast, renameGymnast
and createRoutine had ended up inside doc comments and were missing from
the schema. Also restores the searchCatalog search description from dev
(80 characters, 8 words, text without searchable words finds nothing).
Adding an item that is already in a gymnast's list, or removing one that
is not, now changes nothing and returns the gymnast, so ALREADY_EXISTS is
gone and NOT_FOUND is only for unknown ids. Routine.dScore is null while
the routine has a removed item, the same rule as calculateDScore.
calculateDScore keeps taking only item ids.
Only the layout of the doc comments changes. printSchema gives the same
result before and after.
Sign in to join this conversation on GitHub.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants