Skip to content

Conversation

@laminne
Copy link
Member

@laminne laminne commented Jan 14, 2026

close #476

やったこと

  • リノート/引用ボタンを設置しました
  • リノートを表示できるようにしました

スクリーンショット:

リノートボタンの様子
image

リノートボタンを押したときの挙動
image

リノート/引用の表示
image

laminne and others added 7 commits January 3, 2026 17:57
- SVGアイコンを🔁絵文字に変更
- リノートしたユーザーのアバターを追加
- UI言語を英語に変更(renoted/quoted)
- 引用時は"quoted"、通常リノート時は"renoted"と表示

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- リノートのID、reactionsをリノート自体のものを使用(オリジナルノートではなく)
- 元ノート情報を取得するAPI関数を追加
- タイムラインとアカウントページでリノート表示を改善
- リノートヘッダーコンポーネントを統合

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- コメント入力フォームを追加
- エラーハンドリングを実装
- CSS Modulesを使用したスタイリング
- プレビュー機能を追加

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@laminne laminne self-assigned this Jan 14, 2026
@laminne laminne marked this pull request as draft January 14, 2026 13:17
@laminne laminne requested a review from Copilot January 14, 2026 14:10
@laminne laminne marked this pull request as ready for review January 14, 2026 14:12
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements renote (retweet) and quote functionality for the social media application, allowing users to share posts with or without additional comments.

Changes:

  • Added renote and quote buttons with dropdown menu UI
  • Implemented API routes and handlers for creating renotes/quotes
  • Enhanced timeline and account views to display renoted posts with author information
  • Added utility functions to fetch individual notes by ID

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
app/styles/renote.module.css Styling for the quote renote page form and layout
app/routes/timeline.tsx Modified to fetch and display renoted posts with original note data
app/routes/notes.$id.renote.tsx New route for creating quote renotes with preview
app/routes/api.renote.ts API endpoint for creating renotes without navigation
app/routes/accounts.$id._index.tsx Modified to fetch and display renoted posts on account timelines
app/lib/api/timeline.ts Extended types to support renote fields and original note data
app/lib/api/note.ts New utility for fetching individual notes by ID
app/components/renoteMenu.tsx Dropdown menu component for renote/quote actions
app/components/renoteMenu.module.css Styling for the renote menu dropdown
app/components/renoteHeader.tsx Header component showing who renoted/quoted a post
app/components/renoteHeader.module.css Styling for the renote header
app/components/note.tsx Enhanced to display renote information and quote comments
app/components/note.module.css Added styling for action buttons layout

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

return (
<div key={note.id}>
<Note
id={note.id}
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

The note ID passed to the Note component should be the renote ID (note.id), not the original note ID. This allows users to interact with the specific renote instance. The current implementation in accounts.$id._index.tsx incorrectly passes note.originalNote.id, which would make all renotes of the same original note appear identical and share interactions.

Copilot uses AI. Check for mistakes.

if (isRenote) {
return {
id: note.originalNote.id,
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

The note ID should be note.id (the renote ID), not note.originalNote.id. Using the original note ID means all renotes of the same original note will have the same ID, which breaks the ability to distinguish between different renote instances and could cause React key conflicts.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +35
if (fetcher.state === "loading") {
if ("error" in fetcher.data) {
setIsRenoted(false);
}
}
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

The error handling logic is incorrect. The condition checks if state is "loading", but errors should be handled when the fetcher has completed (state is "idle"). Change the condition to check if fetcher.state === "idle" and if there's an error in the response data, then reset isRenoted to false.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +78
// リノートの元ノート情報を取得
const notesWithOriginal = await Promise.all(
res.notes.map(async (note) => {
if (note.original_note_id) {
const originalNote = await fetchNoteById(
note.original_note_id,
cookie,
basePath
);
if ("error" in originalNote) {
return note;
}
return {
...note,
originalNote: {
id: originalNote.id,
content: originalNote.content,
contents_warning_comment: originalNote.contents_warning_comment,
author: {
id: originalNote.author.id,
name: originalNote.author.name,
display_name: originalNote.author.display_name,
avatar: originalNote.author.avatar,
},
reactions: originalNote.reactions,
},
};
}
return note;
})
);
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

This implementation creates an N+1 query problem. For each note with original_note_id, a separate API call is made to fetch the original note. If the timeline contains many renotes, this will result in many sequential API requests, significantly impacting performance. Consider implementing a batch endpoint on the backend to fetch multiple notes in a single request, or have the timeline API include the original note data directly.

Copilot uses AI. Check for mistakes.
a:hover {
text-decoration: underline;
}
}
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

The quoteComment CSS class is used in the note.tsx component at line 92, but this class is not defined in the module CSS file. This will cause the styling to not be applied to the quote comment element.

Copilot uses AI. Check for mistakes.
setIsRenoted(false);
}
}
}, [fetcher.state]);
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

The useEffect hook is missing fetcher.data in its dependency array. This could cause the effect to not run when fetcher.data changes. Add fetcher.data to the dependency array.

Copilot uses AI. Check for mistakes.
Comment on lines +81 to +111
// リノートの元ノート情報を取得
const timelineWithOriginal = await Promise.all(
timelineRes.map(async (note) => {
if (note.original_note_id) {
const originalNote = await fetchNoteById(
note.original_note_id,
token,
basePath
);
if ("error" in originalNote) {
return note;
}
return {
...note,
originalNote: {
id: originalNote.id,
content: originalNote.content,
contents_warning_comment: originalNote.contents_warning_comment,
author: {
id: originalNote.author.id,
name: originalNote.author.name,
display_name: originalNote.author.display_name,
avatar: originalNote.author.avatar,
},
reactions: originalNote.reactions,
},
};
}
return note;
})
);
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

This implementation creates an N+1 query problem. For each note with original_note_id, a separate API call is made to fetch the original note. If the account timeline contains many renotes, this will result in many sequential API requests, significantly impacting performance. Consider implementing a batch endpoint on the backend to fetch multiple notes in a single request, or have the timeline API include the original note data directly.

Copilot uses AI. Check for mistakes.

if (!res.ok) {
const errorRes = (await res.json()) as { error: string };
console.log("[notes.renote] error:", errorRes.error);
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

Console.log statement should be removed from production code or replaced with a proper logging system. Debug logs in production can expose sensitive information and clutter server logs.

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +50
const noteID = params.id;

const basePath = (context.cloudflare.env as Env).API_BASE_URL;
const res = await fetch(new URL(`/v0/notes/${noteID}/renote`, basePath), {
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

Missing input validation for noteID parameter. If params.id is undefined, it could cause unexpected API behavior or errors. Add validation to ensure noteID exists before making the API call.

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +21
const noteID = params.id;
const basePath = (context.cloudflare.env as Env).API_BASE_URL;

const res = await fetch(new URL(`/v0/notes/${noteID}`, basePath), {
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

Missing input validation for noteID parameter. If params.id is undefined, it could cause unexpected API behavior or errors. Add validation to ensure noteID exists before making the API call.

Copilot uses AI. Check for mistakes.
@laminne laminne enabled auto-merge January 14, 2026 14:27
@laminne laminne marked this pull request as draft January 14, 2026 14:38
auto-merge was automatically disabled January 14, 2026 14:38

Pull request was converted to draft

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: リノート/引用/返信ができるようにする

1 participant