Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions website/src/routes/create/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@
newCharErrorText = '';
reloadCharacters();
} catch (err: any) {
errorText = err.data.data?.message ?? 'Create failed. Please try again.';
newCharErrorText = err.data.data?.message ?? 'Create failed. Please try again.';

const firstKey = Object.keys(err.data.data)[0];
if (firstKey) {
Expand Down Expand Up @@ -200,7 +200,7 @@
<span class="label-text font-medium">Your Character Description</span>
</div>
<textarea
name="characterpDescription"
name="characterDescription"
placeholder="description"
class="textarea w-full"
maxlength="1000"
Expand Down
206 changes: 206 additions & 0 deletions website/src/routes/edit/characters/[id]/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/state';
import pb from '$lib/pocketbase';
import { onMount } from 'svelte';

let charTags = $state<string[]>([]);
let charTagsCurrentInput = $state('');
let charErrorText = $state('');

let record = $state<any>();

function newCharAddTag(e: KeyboardEvent) {
if (e.key === 'Enter' && charTagsCurrentInput.trim()) {
e.preventDefault();
if (charTags.length >= 10) {
charErrorText = 'Maximum of ten tags';
return;
}
// 如果标签不存在则添加
if (!charTags.includes(charTagsCurrentInput.trim())) {
charTags.push(charTagsCurrentInput.trim());
}
charTagsCurrentInput = ''; // 清空输入
}
}

let charName = $state('');
let charDescription = $state('');
let charOrigin = $state('');
let charPictures: FileList | undefined = $state();

const updateCharacter = async () => {
const formData = new FormData();
charErrorText = '';

Comment on lines +33 to +36
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing required-field validation before submit.

Unlike createCharacter (which validates newCharName/newCharDescription/newCharOrigin are non-empty), updateCharacter submits directly. A user who clears the Name or Description field and clicks Update will either get a server-side validation error or successfully persist empty required values, depending on the PocketBase schema. Mirror the create-form validation here for consistent UX.

🛡️ Proposed fix
 	const updateCharacter = async () => {
 		const formData = new FormData();
 		charErrorText = '';
+
+		if (!charName.trim() || !charDescription.trim() || !charOrigin.trim()) {
+			charErrorText = 'All fields must be filled out';
+			return;
+		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const updateCharacter = async () => {
const formData = new FormData();
charErrorText = '';
const updateCharacter = async () => {
const formData = new FormData();
charErrorText = '';
if (!charName.trim() || !charDescription.trim() || !charOrigin.trim()) {
charErrorText = 'All fields must be filled out';
return;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@website/src/routes/edit/characters/`[id]/+page.svelte around lines 33 - 36,
The updateCharacter function is missing client-side required-field
validation—before constructing FormData and submitting, check that newCharName
and newCharDescription (and newCharOrigin if required by schema) are non-empty,
set charErrorText to a descriptive message when any are empty, and abort
(return) to avoid sending an invalid request; mirror the same validation logic
used in createCharacter so the UX matches (use the same field checks and error
messages).

if (charPictures && charPictures.length !== 0) {
for (let file of charPictures) {
formData.append('images', file);
}
}

formData.append('name', charName)
formData.append('description', charDescription)
formData.append('origin', charOrigin)
charTags.forEach((tag) => {
formData.append('tag_names', tag);
});
Comment on lines +37 to +48
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and inspect the file
find . -name "+page.svelte" -path "*/edit/characters/*" -type f

Repository: pdnode-team/cp

Length of output: 112


🏁 Script executed:

# Get the context around lines 37-50 of the file
cat -n "website/src/routes/edit/characters/[id]/+page.svelte" | sed -n '30,55p'

Repository: pdnode-team/cp

Length of output: 966


🏁 Script executed:

# Check if there's any PocketBase version info in the project
find . -name "package.json" -o -name "package-lock.json" -o -name "pocketbase.json" | head -5

Repository: pdnode-team/cp

Length of output: 80


🏁 Script executed:

# Let's check the package.json for dependencies and PocketBase info
cat "package.json" 2>/dev/null || cat "website/package.json" 2>/dev/null || echo "No package.json found in root"

Repository: pdnode-team/cp

Length of output: 1320


🌐 Web query:

PocketBase 0.26.8 update multi-value field empty array relations

💡 Result:

No specific changes or fixes related to multi-value (multiple relation) fields and empty array relations were made in PocketBase v0.26.x series, including any potential v0.26.8 release. The v0.26.x releases (v0.26.0 through v0.26.6 confirmed) focus on dependency updates, minor bug fixes like error serialization and OIDC support, AWS SDK replacement, and timeout adjustments, but nothing mentions relation fields or empty arrays. Multi-value relation fields in PocketBase are stored as JSON arrays of record IDs. Empty relations are represented as empty arrays []. Known past issues with multi-relations (e.g., API rules :length operator not recognizing empty strings as empty arrays, fixed in v0.16.10; multipart form binding fixed in v0.16.6) predate v0.26.x. Official docs confirm standard handling: set to [] for empty, use field+/field- modifiers for append/remove. No evidence of bugs or changes in v0.26.8 specifically addressing "empty array relations". If experiencing issues, check field defaults (toggle single/multiple to normalize), API rules, or client-side sending (e.g., empty array vs empty string).

Citations:


🌐 Web query:

PocketBase FormData update field clearing convention unset

💡 Result:

In PocketBase, record updates via the API (PATCH /api/collections/{collection}/records/{id}) are partial updates: only fields explicitly included in the request body are processed and potentially changed. Fields omitted from the FormData or JSON body retain their existing database values unchanged. To intentionally clear or unset a field during an update (set it to null/empty/zero-value), explicitly include it in the request body with the desired clearing value: - For most scalar fields (text, number, etc.): use empty string "" or null (if the field supports null and is not required). - For file fields: use empty string "" or [] (empty array) when using FormData to clear all files. Examples from official docs: formData.append('documents', '') or { 'documents': [] }. - For JSON fields: send null, empty object {}, or empty array [] as appropriate. - For relations and arrays: use [] for empty, or field- modifier (e.g., 'tags-': ['id1']) to remove specific items. This behavior is confirmed in the RecordUpsert form source code, where only submitted fields (via Load(data)) call record.SetIfFieldExists(k, v), leaving omitted fields untouched. Official file handling docs explicitly state setting file fields to empty string/[] in FormData clears them. JS SDK examples and API reference show update(body) processes only provided fields. Example clearing a text field with FormData: const formData = new FormData; formData.append('title', ''); // clears title formData.append('otherField', 'keep this'); await pb.collection('example').update('RECORD_ID', formData); Omitting 'title' entirely would leave it unchanged.

Citations:


🏁 Script executed:

# Check if there's any local documentation or examples in the repo about PocketBase usage
find . -name "*.md" -o -name "*.txt" | xargs grep -l "pocketbase\|PocketBase" 2>/dev/null | head -5

Repository: pdnode-team/cp

Length of output: 40


🏁 Script executed:

# Look for any other places where FormData is used with PocketBase update calls to see patterns
rg "formData\.(append|delete)" -A 2 -B 2 --type svelte

Repository: pdnode-team/cp

Length of output: 88


🏁 Script executed:

# Look for any backend/schema files that might define field types
find . -name "*.go" -o -name "*.pb" -o -name "schema*" -o -name "*.sql" | head -10

Repository: pdnode-team/cp

Length of output: 421


🏁 Script executed:

# Search for any references to 'tag_names' or 'images' fields to understand their types
rg "tag_names|images" -t svelte -B 3 -A 3 | head -50

Repository: pdnode-team/cp

Length of output: 88


🏁 Script executed:

# Check if there's any documentation about the character collection schema
find . -name "*.md" | xargs grep -l "tag_names\|character.*schema" 2>/dev/null | head -5

Repository: pdnode-team/cp

Length of output: 40


🏁 Script executed:

# Look for any API route handlers or backend setup that might define fields
find . -type f -name "*.ts" -o -name "*.js" | xargs grep -l "characters\|collection" 2>/dev/null | head -10

Repository: pdnode-team/cp

Length of output: 40


🏁 Script executed:

# Examine the character creation migration
cat -n "./migrations/1776132769_created_characters.go"

Repository: pdnode-team/cp

Length of output: 5073


🏁 Script executed:

# Examine the latest character update migration
cat -n "./migrations/1776489282_updated_characters.go"

Repository: pdnode-team/cp

Length of output: 2362


formData.delete(...) calls are no-ops on a freshly constructed FormData.

formData is created on line 34 and nothing is appended to images or tag_names before the delete() calls on lines 38 and 47. Both deletions do nothing. PocketBase performs partial updates—only fields explicitly included in the request are processed; omitted fields retain their existing values.

Current behavior:

  • Uploading no new pictures leaves existing images untouched (likely intentional, but the delete is dead code).
  • Removing all tags (charTags = []) will NOT clear them on the server because tag_names is never appended to FormData when the array is empty, leaving existing tags unchanged.

The tag_names field (JSON type) must always be sent to replace its value on the server. When charTags is empty, explicitly append an empty value to FormData to clear the field:

♻️ Suggested fix
 		if (charPictures && charPictures.length !== 0) {
-            formData.delete('images')
 			for (let file of charPictures) {
 				formData.append('images', file);
 			}
 		}
 
 		formData.append('name', charName);
 		formData.append('description', charDescription);
 		formData.append('origin', charOrigin);
-        formData.delete('tag_names')
-		charTags.forEach((tag) => {
-			formData.append('tag_names', tag);
-		});
+		// Always send tag_names to replace the value, even when empty.
+		if (charTags.length === 0) {
+			formData.append('tag_names', '');
+		} else {
+			charTags.forEach((tag) => formData.append('tag_names', tag));
+		}

Verify with your PocketBase 0.26.8 backend whether an empty string (''), empty JSON array ('[]'), or another format correctly clears the JSON tag_names field during updates.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (charPictures && charPictures.length !== 0) {
formData.delete('images')
for (let file of charPictures) {
formData.append('images', file);
}
}
formData.append('name', charName);
formData.append('description', charDescription);
formData.append('origin', charOrigin);
formData.delete('tag_names')
charTags.forEach((tag) => {
formData.append('tag_names', tag);
});
if (charPictures && charPictures.length !== 0) {
for (let file of charPictures) {
formData.append('images', file);
}
}
formData.append('name', charName);
formData.append('description', charDescription);
formData.append('origin', charOrigin);
// Always send tag_names to replace the value, even when empty.
if (charTags.length === 0) {
formData.append('tag_names', '');
} else {
charTags.forEach((tag) => formData.append('tag_names', tag));
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@website/src/routes/edit/characters/`[id]/+page.svelte around lines 37 - 50,
Remove the no-op formData.delete calls and ensure tag_names is always sent: drop
formData.delete('images') and formData.delete('tag_names'), keep the existing
images append logic (only append files when charPictures is non-empty), and
change the tag handling in the block that uses charTags so that if
charTags.length === 0 you explicitly append a clearing value for tag_names (e.g.
append('tag_names', '[]') or '' depending on your PocketBase backend) otherwise
append each tag as before; test with your PocketBase 0.26.8 to confirm whether
'[]' or '' correctly clears the JSON tag_names field.


formData.append('owner', record.owner);

try {
await pb.collection('characters').update(page.params.id!, formData);
goto(`/characters/${page.params.id}`);
} catch (err: any) {
charErrorText = err.data.data?.message ?? 'Update failed. Please try again.';

const firstKey = Object.keys(err.data.data)[0];
if (firstKey) {
const friendlyMessage = `${firstKey}: ${err.data.data[firstKey].message}`;
console.error(friendlyMessage);
charErrorText = friendlyMessage;
return;
}
}
};

onMount(async () => {
if (!pb.authStore.isValid) {
window.location.pathname = '/login';
return;
}
record = await pb.collection('characters').getOne(page.params.id!);

let rawTags = record.tag_names;

try {
// 如果它是字符串,尝试解析它
if (typeof rawTags === 'string') {
// 如果 rawTags 是 '"单相思"',解析后得到 '单相思'
// 如果 rawTags 是 '["A", "B"]',解析后得到 ['A', 'B']
const parsed = JSON.parse(rawTags);

// 解析后判断结果:是数组直接赋值,是字符串则包装成数组
if (Array.isArray(parsed)) {
charTags = parsed;
} else if (typeof parsed === 'string') {
charTags = [parsed];
}
} else if (Array.isArray(rawTags)) {
// 如果 SDK 已经自动帮你转成了数组
charTags = rawTags;
}
} catch (e) {
// 如果解析失败(比如 rawTags 本身就是个普通字符串 "单相思" 而不是 '"单相思"')
charTags = rawTags ? [rawTags] : [];
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

charName = record.name;
charDescription = record.description;
charOrigin = record.origin;
});
</script>

<div class="flex min-h-[70vh] flex-col items-center justify-center px-4 py-12">
<div class="card w-full max-w-md bg-base-100">
{#if record}
<div class="card-body flex flex-col gap-4 p-8">
<h2 class="mb-2 text-center text-3xl font-bold">Update {record?.name}</h2>
<p class="mb-6 text-center text-base-content/60">
All fields must be filled out unless otherwise specified.
</p>
<div class="flex flex-col gap-4">
<label class="form-control w-full">
<div class="label"><span class="label-text font-medium">Name</span></div>
<input
type="text"
name="characterName"
placeholder="XXXX & YYYY"
class="input-bordered input w-full"
autocomplete="name"
bind:value={charName}
required
/>
</label>

<label class="form-control w-full">
<div class="label">
<span class="label-text font-medium">Description</span>
</div>
<textarea
name="characterDescription"
placeholder="description"
class="textarea w-full"
maxlength="1000"
bind:value={charDescription}
required
></textarea>
</label>

<label class="form-control w-full">
<div class="label"><span class="label-text font-medium">Origin</span></div>
<input
type="text"
name="characterOrigin"
placeholder="https://pdnode.com"
class="input-bordered input w-full"
autocomplete="name"
bind:value={charOrigin}
/>
</label>

<div class="form-control w-full max-w-xs">
<label class="label" for="file-upload">
<span class="label-text">Pictures (Option)</span>
</label>

<input
type="file"
id="file-upload"
class="file-input-bordered file-input w-full file-input-primary"
accept="image/*"
multiple
bind:files={charPictures}
/>
</div>

<div class="flex w-full max-w-sm flex-col">
<div class="label">
<span class="label-text font-medium">Tags (Option)</span>
</div>

<input
type="text"
placeholder="Enter the tags and press Enter...."
class="input-bordered input w-full"
bind:value={charTagsCurrentInput}
onkeydown={newCharAddTag}
/>

<div class="mt-2 flex flex-wrap gap-2">
{#each charTags as tag, i}
<div class="badge gap-2 badge-soft p-3 badge-primary">
{tag}
<button type="button" onclick={() => charTags.splice(i, 1)} class="text-xs"
>✕</button
>
</div>
{/each}
</div>
</div>

<p class="text-red-500">{charErrorText}</p>

<button type="submit" class="btn w-full text-lg btn-primary" onclick={updateCharacter}
>Update</button
>
</div>
</div>
{:else}
<div role="alert" class="alert alert-soft alert-error">
<span>The record could not be found.</span>
</div>
{/if}
</div>
</div>
2 changes: 1 addition & 1 deletion website/src/routes/edit/cps/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
const formData = new FormData();
formData.append('name', cpName);
formData.append('description', cpDescription);
formData.append('owner', pb.authStore.record!.id);
formData.append('owner', record.owner);
if (cpPictures && cpPictures.length > 0) {
for (let file of cpPictures) {
formData.append('images', file);
Expand Down