# Comment localStorage Persistence + Pending Visibility
## Problem
After submitting a comment, users see only a green "评论已提交,等待审核" alert. The comment is invisible to everyone (including the author) until admin approval. Additionally, returning users must re-enter their name/email/website every time.
## Solution
Two localStorage-backed features:
1. **Form auto-fill**: Save author info (name/email/url) to localStorage, pre-fill on subsequent visits.
2. **Pending comment visibility**: Store pending comments locally by server-returned ID. Display them with a "审核中" badge, visible only to the submitting browser.
## localStorage Keys
### `yggdrasil-comment-author`
Written on every successful comment submission. Read on `CommentForm` mount.
```json
{ "name": "张三", "email": "zhang@example.com", "url": "https://example.com" }
```
### `yggdrasil-pending-comments`
Written on successful submission with server-returned ID. Read on `CommentSection` mount. Keyed by `post_id` (string).
```json
{
"42": [
{
"id": 123,
"parent_id": null,
"depth": 0,
"author_name": "张三",
"author_url": null,
"avatar_url": "https://cravatar.cn/avatar/xxx?d=mp&s=80",
"content_md": "评论内容",
"created_at": "2026-06-11T10:00:00Z",
"stored_at": "2026-06-11T10:00:00Z"
}
]
}
```
Design decisions:
- **No `content_html`** — XSS safety. Render `content_md` client-side with HTML escaping + newline→` `.
- **No `author_email`** — Already in `yggdrasil-comment-author`. Avoid PII duplication.
- **`avatar_url`** computed at save time from the stored author email.
- **`stored_at`** for 7-day TTL. Pruned on every read.
- Empty post_id arrays removed on cleanup.
## Server-Side Changes
### 1. `CommentResponse` — add `comment_id`
```rust
pub struct CommentResponse {
pub success: bool,
pub message: String,
pub error_code: Option,
pub comment_id: Option, // NEW: set only on success
}
```
Backward-compatible: `Option` serde defaults to `None` for old callers.
### 2. `create_comment` — extract and return ID
The existing INSERT already uses `RETURNING id` but discards the row. Change to:
```rust
let row = client
.query_one("INSERT INTO comments ... RETURNING id", &[...])
.await
.map_err(AppError::query)?;
let comment_id: i64 = row.get(0);
```
All existing error paths add `comment_id: None`. Only the final success path sets `comment_id: Some(comment_id)`.
### 3. New server function: `CheckPendingStatus`
```rust
#[server(CheckPendingStatus, "/api")]
pub async fn check_pending_status(ids: Vec) -> Result, ServerFnError>
```
- Early return empty vec if `ids.is_empty()`.
- Query: `SELECT id, status FROM comments WHERE id = ANY($1)`
- IDs not found in result → status `"gone"` (soft-deleted or hard-deleted).
- Client uses this to prune localStorage entries that are no longer pending.
## Client-Side Changes
### New module: `src/hooks/comment_storage.rs`
Provides `use_comment_storage()` hook with:
| Function | Purpose |
|----------|---------|
| `save_author(name, email, url)` | Write `yggdrasil-comment-author` |
| `load_author() -> Option` | Read author info |
| `save_pending_comment(post_id, comment)` | Append to `yggdrasil-pending-comments` |
| `load_pending_comments(post_id) -> Vec` | Read + prune expired (7-day TTL) |
| `remove_pending_ids(post_id, ids)` | Remove specific IDs, clean empty post entries |
| `prune_expired()` | Remove all entries across all posts older than 7 days |
All `web_sys` calls behind `#[cfg(target_arch = "wasm32")]`. Non-WASM returns defaults (empty/None).
Serialization via `serde_json` (already a project dependency).
### `CommentContext` — extend with pending state
```rust
#[derive(Clone, Copy)]
pub struct CommentContext {
pub active_reply: Signal