fix(frontend): submit-time data flow (rating scale, table reactivity)

- rating submitted doubled: the * 2 belonged to the retired 0-5
  slider scale while StarInput already works in backend 0-10 units,
  so every rating arrived doubled (9 became 18, both backends
  rejected it); form value now passes through unscaled, display and
  words map already native 0-10
- StarInput gains an optional name prop rendering a hidden input,
  restoring the rating form field the old slider exposed; Rating
  passes name="rating"
- table updates go immutable (new array every path): in-place
  mutation with the same reference never reliably invalidated the
  Svelte 5 $derived, which left the grid stale after successful
  submits; replace-or-append semantics unchanged
- per-page dropdown reads the event value synchronously (Svelte 5
  nulls currentTarget after dispatch, crashing the old
  read-inside-.then)
- detail poster column shares a flex centering context so poster and
  stars align by construction at every width
This commit is contained in:
John O'Keefe
2026-09-16 16:30:23 -04:00
parent 515678f035
commit e2c8ba10ce
5 changed files with 31 additions and 21 deletions
+3 -1
View File
@@ -169,7 +169,9 @@
for (let field of formData) {
const [key, value] = field;
if (key === "rating") {
submitData.rating = Number(value) * 2;
// Form value is already 0-10 (StarInput works in backend units).
// The old * 2 belonged to the retired 0-5 slider scale.
submitData.rating = Number(value);
continue;
}
if (key === "episodes") {
@@ -43,14 +43,14 @@
function changeCountPerPage(
e: Event & { currentTarget: HTMLSelectElement },
): void {
App.GetAniListUserWatchingList(1, Number(e.currentTarget.value), sort).then(
(result) => {
animePerPage.set(Number(e.currentTarget.value));
// Read synchronously: Svelte 5 nulls currentTarget after dispatch.
const count = Number(e.currentTarget.value);
App.GetAniListUserWatchingList(1, count, sort).then((result) => {
animePerPage.set(count);
watchListPage.set(1);
aniListWatchlist.set(result);
aniListLoggedIn.set(true);
},
);
});
}
</script>
+1 -1
View File
@@ -19,7 +19,7 @@
</script>
<div>
<StarInput bind:value={score} min={0} max={10} step={1} />
<StarInput bind:value={score} min={0} max={10} step={1} name="rating" />
<p>Rating: {score}</p>
<p>{ratingInWords[score]}</p>
</div>
@@ -3,19 +3,19 @@
import { tableItems } from "./GlobalVariablesAndHelperFunctions.svelte"
export function AddAnimeServiceToTable(animeItem: TableItem) {
// Always return a NEW array: in-place mutation with the same
// reference does not reliably invalidate $derived consumers
// under Svelte 5, which left the table stale after submits.
tableItems.update((table) => {
if (table.length === 0) {
table.push(animeItem)
} else {
for (const [index, tableItem] of table.entries()) {
if(tableItem.service === animeItem.service) {
table[index] = animeItem
return table
const index = table.findIndex(
(tableItem) => tableItem.service === animeItem.service,
);
if (index === -1) {
return [...table, animeItem];
}
}
table.push(animeItem)
}
return table
return table.map((tableItem, i) =>
i === index ? animeItem : tableItem,
);
})
}
</script>
@@ -21,6 +21,7 @@
step = 0.5,
count = 5,
disabled = false,
name,
starConfig = {
size: 32,
fillColor: '#F9ED4F',
@@ -36,6 +37,10 @@
step?: number;
count?: number;
disabled?: boolean;
// When set, renders a hidden input so the value submits with a
// surrounding <form> via FormData (the old slider exposed
// name="rating" the same way).
name?: string;
starConfig?: StarColors;
onchange?: (value: number) => void;
} = $props();
@@ -99,6 +104,9 @@
onmouseleave={onHoverLeave}
>
<div class="stars">
{#if name !== undefined}
<input type="hidden" name={name} value={value} />
{/if}
{#each Array(count) as _, i}
<button
type="button"