{"id":11601,"date":"2026-10-05T06:58:28","date_gmt":"2026-10-04T21:58:28","guid":{"rendered":"https:\/\/www.freelifemakers.org\/wordpress\/?p=11601"},"modified":"2026-10-05T11:45:18","modified_gmt":"2026-10-05T02:45:18","slug":"nextjs-sns-server-46","status":"publish","type":"post","link":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/10\/05\/nextjs-sns-server-46\/","title":{"rendered":"[nextjs]SNS Server-46(myapp56)\u00a0"},"content":{"rendered":"\n<p>\ud83d\udc49\ud83c\udffb myapp56\uc5d0\uc11c\ub294 \ud0c0\uc784\ub77c\uc778 \uae00\ubcf4\uae30\ub97c  Home \/ Local \/ Federated \ud0ed\uc73c\ub85c   \ubcfc \uc218 \uc788\uc2b5\ub2c8\ub2e4.<br>In myapp56, you can view the timeline under the Home, Local, and Federated tabs.<\/p>\n\n\n\n<p>\ud83d\udc49\ud83c\udffb \uc804\uccb4 \ucf54\ub4dc\ub294 \uae43\ud5c8\ube0c\uc5d0\uc11c \ud655\uc778 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.<br>You can find the full code on GitHub.<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/gideonslife01\/flm-nextjs\">https:\/\/github.com\/gideonslife01\/flm-nextjs<\/a><\/p>\n\n\n\n<p>\ud83d\udcc1 \uc804\uccb4 \ud504\ub85c\uc81d\ud2b8 \uad6c\uc870 \/ Overall Project Structure<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>myapp project\/  \n\u251c\u2500\u2500 app\/  (Next.js App Router)\n\u2502   \u251c\u2500\u2500 .well-known\/webfinger\/route.ts  -&gt; webfinger\n\u2502   \u251c\u2500\u2500 api\/follow\/route.ts    -&gt; Follow API(temporary)\n\u2502   \u251c\u2500\u2500 api\/announce\/route.ts  -&gt; Boost(Announcement)\n\u2502   \u251c\u2500\u2500 api\/like\/route.ts.     -&gt; Like API\n\u2502   \u251c\u2500\u2500 api\/posts\/route.ts     -&gt; Writing API(deprecated)\n\u2502   \u251c\u2500\u2500 api\/timeline\/route.ts  -&gt; Timeline API\n\u2502   \u251c\u2500\u2500 api\/followinglist\/route.ts  -&gt; followinglist API\n\u2502   \u251c\u2500\u2500 api\/followerslist\/route.ts  -&gt; followerslist API\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">api\/v1\/instance<\/span>\/route.ts -&gt; auth\n\u2502   \u251c\u2500\u2500 api\/v1\/apps\/route.ts.    -&gt; auth\n\u2502   \u251c\u2500\u2500 api\/v1\/accounts\/verify_credentials\/route.ts -&gt; auth\n\u2502   \u251c\u2500\u2500 api\/v1\/statuses\/route.ts -&gt; Write Post\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">api\/v1\/timelines\/home\/route.ts<\/span> -&gt; timeline\n\u2502   \u251c\u2500\u2500 api\/v1\/media\/route.ts -&gt; Image upload\n\u2502   \u251c\u2500\u2500 api\/v1\/media\/&#91;id]\/route.ts  -&gt; Image Delete \n\u2502   \u251c\u2500\u2500 api\/v1\/instance\/rules\/route.ts -&gt; Feature not implemented.\n\u2502   \u251c\u2500\u2500 api\/v1\/instance\/domain_blocks\/route.ts -&gt; Feature not implemented.\n\u2502   \u251c\u2500\u2500 api\/v1\/filters\/route.ts -&gt; Feature not implemented.\n\u2502   \u251c\u2500\u2500 api\/v1\/lists\/route.ts -&gt; Feature not implemented.\n\u2502   \u251c\u2500\u2500 api\/v1\/announcements\/route.ts -&gt; Feature not implemented.\n\u2502   \u251c\u2500\u2500 api\/v1\/timelines\/public\/route.ts -&gt; Feature not implemented.\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">oauth\/authorize\/route.ts<\/span> -&gt; auth\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">oauth\/token\/route.ts<\/span> -&gt; auth\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">oauth\/login-and-authorize\/route.ts<\/span> -&gt; Tursky,Pinafore login processing\n\u2502   \u251c\u2500\u2500 oauth\/revoke\/route.ts -&gt; Feature not implemented.\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">api\/v1\/search\/home\/route.ts<\/span> -&gt; search\n\u2502   \u251c\u2500\u2500 <span style=\"background-color: initial; font-size: inherit; text-align: initial; color: initial;\">api\/v2\/search\/home\/route.ts<\/span> -&gt; search\n\u2502   \u251c\u2500\u2500 api\/v2\/notifications\/policy\/route.ts-&gt; Feature not implemented.\n\u2502   \u251c\u2500\u2500 api\/v1\/accounts\/&#91;id]\/followers\/route.ts -&gt; followers\n\u2502   \u251c\u2500\u2500 api\/v1\/accounts\/&#91;id]\/following\/route.ts -&gt; following\n\u2502   \u251c\u2500\u2500 api\/v1\/statuses\/&#91;id]\/route.ts -&gt; -&gt; Post delete(pinafore)\n\u2502   \u251c\u2500\u2500 api\/auth\/signup\/route.ts        -&gt; Website Signup\n\u2502   \u251c\u2500\u2500 api\/auth\/login\/route.ts         -&gt; Website Login\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/auth\/logout\/route.ts        -&gt; Website Logout\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/auth\/refresh\/route.ts       -&gt; Refresh Token\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/cleanup-orphan\/route.ts     -&gt; post,outbox clean up\n\u2502   \u251c\u2500\u2500<strong> <\/strong>api\/auth\/me\/route.tsx           -&gt; Login Check<strong>\n<\/strong>\u2502   \u251c\u2500\u2500 auth\/signup\/page.tsx            -&gt; Signup UI\n\u2502   \u251c\u2500\u2500 auth\/signup\/page.tsx            -&gt; Login UI        \n\u2502   \u251c\u2500\u2500 users\/&#91;username]\/\n\u2502   \u2502   \u251c\u2500\u2500 statuses\/&#91;id]\/route.ts -&gt; Indivisual Post\n\u2502   \u2502   \u251c\u2500\u2500 route.ts               -&gt; Acotr Information\n\u2502   \u2502   \u251c\u2500\u2500 followers\/route.ts     -&gt; Followers List\n\u2502   \u2502   \u251c\u2500\u2500 following\/route.ts     -&gt; Following List\n\u2502   \u2502   \u251c\u2500\u2500 inbox\/route.ts         -&gt; Inbox\n\u2502   \u2502   \u2514\u2500\u2500 outbox\/route.ts        -&gt; outbox\n\u2502   \u251c\u2500\u2500 usersui\/&#91;username]\/\n\u2502   \u2502   \u251c\u2500\u2500 page.tsx               -&gt; Timeline UI(Server, 404)\n\u2502   \u2502   \u251c\u2500\u2500 ClientPage.tsx.        -&gt; Timeline UI(Client,Call Active Themes)\n\u2502   \u2502   \u2514\u2500\u2500 _components\/themes\/\n\u2502   \u2502       \u251c\u2500\u2500 themeex\/ThemeexTheme.tsx   -&gt; Example Theme\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/PinaforeTheme.tsx -&gt; Theme 1\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/FollowersList.tsx -&gt; Theme 1, FollowersList\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/FollowingList.tsx -&gt; Theme 1, FollowingList\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/NotificationsList.tsx -&gt; Theme 1, NotificationsList\n\u2502   \u2502       \u251c\u2500\u2500 pinafore\/TimelineList.tsx  -&gt; Theme 1, TimelineList\n\u2502   \u2502       \u251c\u2500\u2500 mastodon\/MastodonTheme.tsx -&gt; Theme 2\n\u2502   \u2502       \u2514\u2500\u2500 minimal\/MinimaltTheme.tsx  -&gt; Theme 3\n\u2502   \u251c\u2500\u2500 layout.tsx, page.tsx, globals.css\n\u2502   \u2514\u2500\u2500 favicon.ico\n\u251c\u2500\u2500 lib\/\n\u2502   \u251c\u2500\u2500 theme.tsx              -&gt; Theme Provider\n\u2502   \u251c\u2500\u2500 watchThemes.ts         -&gt; Check real-time theme changes\n\u2502   \u251c\u2500\u2500 auth.ts                -&gt; Authentication, User Management\n\u2502   \u251c\u2500\u2500 ap.ts                  -&gt; Follow,Undo,Create,Likes,Announce\n\u2502   \u251c\u2500\u2500 visibility.ts          -&gt; Visibility(Public,Unlisted,Private,Direct)\n\u2502   \u2514\u2500\u2500 db.ts                  -&gt; DB connection\n\u251c\u2500\u2500 data\/\n\u2502   \u251c\u2500\u2500 keys\/userIDs\/          -&gt; private.pem, public.pem(New)\n\u2502   \u2514\u2500\u2500 keys\/                  -&gt; private.pem, public.pem(legacy)\n\u251c\u2500\u2500 data.sqlite                -&gt; Database(1\/3)\n\u251c\u2500\u2500 data.sqlite-wal            -&gt; Database(2\/3)\n\u251c\u2500\u2500 data.sqlite-shm            -&gt; Database(3\/3)\n\u251c\u2500\u2500 Caddyfile                  -&gt; https \n\u251c\u2500\u2500 instrumentation.ts         -&gt; Background Server\n\u2514\u2500\u2500 package.json<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ud504\ub85c\uc81d\ud2b8 \uc2dc\uc791 \/ Project Start<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-wp-embed is-provider-freelifemakers-org wp-block-embed-freelifemakers-org\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"wp-embedded-content\" data-secret=\"ioV7EotWd2\"><a href=\"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/09\/03\/nextjs-project-summary\/\">[nextjs]\ud504\ub85c\uc81d\ud2b8\uc694\uc57d\/project summary(myapp30+)<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; visibility: hidden;\" title=\"&#8220;[nextjs]\ud504\ub85c\uc81d\ud2b8\uc694\uc57d\/project summary(myapp30+)&#8221; &#8212; freelifemakers.org\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/2026\/09\/03\/nextjs-project-summary\/embed\/#?secret=C2wMoBFaMi#?secret=ioV7EotWd2\" data-secret=\"ioV7EotWd2\" width=\"560\" height=\"315\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>\ud83d\udcc1 \uae30\ub2a5\uc124\uba85 \/ Feature Description<\/p>\n\n\n\n<p>\u2714\ufe0f Home<\/p>\n\n\n\n<p>&#8212;  \ud398\uc774\uc9c0 \uc18c\uc720\uc790\uac00 \ub85c\uadf8\uc778 \ud55c \uacbd\uc6b0 public, unlisted, private \ubaa8\ub450 \ubcfc \uc218 \uc788\uc74c<br>If the page owner is logged in, they can view public, unlisted, and private content.<\/p>\n\n\n\n<p>&#8212;  \ub0b4\uac00 \ud314\ub85c\uc6b0 \ud55c \uc0ac\ub78c \uae00\uc744 \ubcfc \uc218 \uc788\uc74c.(\ub0b4 \uc11c\ubc84\ub85c \ubc30\ub2ec\ub41c \uae00)<br>You can view posts from people you follow (posts delivered to your server).<\/p>\n\n\n\n<p>&#8212; Unlisted\ub294 Local\uacfc Pederated\uc5d0 \uacf5\uac1c \uc548\ub428<br>Unlisted items are not visible in the Local or Federated timelines.<\/p>\n\n\n\n<p>\u2714\ufe0f Local<\/p>\n\n\n\n<p>&#8212; \ub0b4 \uc11c\ubc84\uc758 Public\uae00\uc740 \uc804\ubd80 \ubcf4\uc784.<br>All public posts on my server are visible.<\/p>\n\n\n\n<p>\u2714\ufe0f Federated<\/p>\n\n\n\n<p>&#8212; \ub0b4 \uc11c\ubc84\uc640 \uc5f0\uacb0\ub41c \ub2e4\ub978 \uc11c\ubc84\uc758 Public\uae00\uc740 \uc804\ubd80 \ubcf4\uc784.<br>All public posts from other servers connected to my server are visible.<\/p>\n\n\n\n<p>\u2714\ufe0f Private<\/p>\n\n\n\n<p>&#8212; \ud398\uc774\uc9c0 \uc18c\uc720\uc790\ub97c \ud314\ub85c\uc6b0 \ud55c \uc0ac\ub78c\ub9cc \ubcfc \uc218 \uc788\uc74c<br>Visible only to people who follow the page owner.<\/p>\n\n\n\n<p>\u2714\ufe0f Direct<\/p>\n\n\n\n<p>&#8212; \ud398\uc774\uc9c0 \uc18c\uc720\uc790+\uba58\uc158\ub41c \uc0ac\ub78c\uc774 \ubcfc \uc218 \uc788\uc74c.<br>Visible to the page owner and mentioned individuals.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>\ud83d\udcc1 \ucf54\ub4dc\uc218\uc815 \/ Code Modification<\/p>\n\n\n\n<p>\ud83d\udca1 \uc544\ub798 \ub77c\uc6b0\ud2b8\ub294 \uad6c\ud604\ub418\uc9c0 \uc54a\uc740 \ub9c8\uc2a4\ud1a0\ub3c8 \ud638\ud658 \ub77c\uc6b0\ud2b8\ub85c \uac00\uc7a5 \ub098\uc911\uc5d0 \uad6c\ud604 \uc608\uc815<br>The route below is a Mastodon-compatible route that has not yet been implemented and is scheduled for implementation last.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>api\/v1\/instance\/rules\/route.ts \napi\/v1\/instance\/domain_blocks\/route.ts\napi\/v1\/filters\/route.ts\napi\/v1\/lists\/route.ts \napi\/v1\/announcements\/route.ts\napi\/v1\/timelines\/public\/route.ts\noauth\/login-and-authorize\/route.ts\napi\/v2\/notifications\/policy\/route.ts<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>\u2714\ufe0f API\uc218\uc815 \/ API Modification<\/p>\n\n\n\n<p>&#8212; app\/api\/timeline\/route.ts &#8211;  \ucf54\ub4dc \uc218\uc815 \/ Code Modification<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>... ...\nexport async function GET(req: Request) {\n... ...\nconst type = searchParams.get('type') || 'home'; \/\/ \u2705 myapp56 - add : home, local, federated\n... ...\n  \/\/ \u2705 myapp56 - add type: home, local, federated\n  let rawTimeline: any&#91;] = &#91;];\n\n  if (type === 'local') {\n    \/\/ - Local: My server posts( visibility == public )\n    rawTimeline = db.prepare(`\n      SELECT id, content, username, username as actor, created_at, visibility, 'Note' as type, 'mine' as source, id as original_id\n      FROM posts WHERE visibility='public'\n      ORDER BY created_at DESC LIMIT 80\n    `).all() as any&#91;];\n\n  } else if (type === 'federated') {\n    \/\/ - Federated: posts + inbox_posts (visibivlity == public)\n    rawTimeline = db.prepare(`\n      SELECT id, content, username, username as actor, created_at, visibility, 'Note' as type, 'mine' as source, id as original_id\n      FROM posts WHERE visibility='public'\n      UNION ALL\n      SELECT id, content, username, actor, created_at, visibility, type, 'inbox' as source, original_id\n      FROM inbox_posts WHERE visibility='public'\n      ORDER BY created_at DESC LIMIT 80\n    `).all() as any&#91;];\n\n  } else {\n    \/\/ - Home: \ub0b4 \ud0c0\uc784\ub77c\uc778\/My timeline + inbox_posts + posts\n    rawTimeline = db.prepare(`\n      SELECT id, content, username, username as actor, created_at, visibility, 'Note' as type, 'mine' as source, id as original_id\n      FROM posts WHERE username =?\n      UNION ALL\n      SELECT id, content, username, actor, created_at, visibility, type, 'inbox' as source, original_id\n      FROM inbox_posts WHERE username =?\n      ORDER BY created_at DESC LIMIT 80\n    `).all(username, username) as any&#91;];\n  }\n... ...\n}<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f \ud14c\ub9c8 \ud30c\uc77c \uc218\uc815 \/ Edit theme files<\/p>\n\n\n\n<p>&#8212;  app\/usersui\/[username]\/_components\/themes\/pinafore\/TimelineList.tsx<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>\/\/ \u2705 myapp56 - Hom,Local,Federated \ud0ed \ucd94\uac00 \/ Add Hom, Local, Federated Tabs\ntype TabType = 'home' | 'local' | 'federated';\ntype Props = {\n  timeline: any&#91;];\n  currentUser: string | null;\n  tab: TabType;\n  onTabChange: (t: TabType) =&gt; void;\n  onDelete: (id: string) =&gt; void;\n  onBoost: (p: any) =&gt; void;\n  onLike: (p: any) =&gt; void;\n  onShare: (p: any) =&gt; void;\n};\n\nexport default function TimelineList({ \n  timeline, currentUser, tab, onTabChange, onDelete, onBoost, onLike, onShare }: Props) {\n  const &#91;showDropdown, setShowDropdown] = useState(false); \/\/ \u2705 myapp56 - Hom,Local,Federated \ud0ed \ucd94\uac00 \/ Add Hom, Local, Federated Tabs\n\n... ...\n  return (\n    &lt;&gt;\n      &lt;style&gt;{`\n             \/* \u2705 myapp56 - timeline source tabs *\/\n            .timeline-dropdown-wrap {\n              display: flex;\n              justify-content: space-between;\n              align-items: center;\n              padding: 10px 16px;\n              border-bottom: 1px solid #e6ecf0;\n              background: white;\n              position: sticky;\n              top: 0;\n              z-index: 5;\n            }\n            .timeline-dropdown { position: relative; }\n            .dropdown-toggle {\n              display: flex;\n              align-items: center;\n              gap: 8px;\n              padding: 8px 14px;\n              border: 1px solid #e6ecf0;\n              border-radius: 20px;\n              background: white;\n              cursor: pointer;\n              font-size: 14px;\n              font-weight: 600;\n            }\n            .dropdown-toggle:hover { background: #f3f4f6; }\n            .dropdown-arrow { font-size: 10px; color: #999; margin-left: 4px; }\n            .dropdown-menu {\n              position: absolute;\n              top: 100%;\n              left: 0;\n              margin-top: 8px;\n              width: 220px;\n              background: white;\n              border: 1px solid #e6ecf0;\n              border-radius: 12px;\n              box-shadow: 0 8px 24px rgba(0,0,0,0.12);\n              overflow: hidden;\n              z-index: 10;\n            }\n            .dropdown-menu button {\n              width: 100%;\n              display: flex;\n              gap: 12px;\n              align-items: center;\n              padding: 12px 14px;\n              border: none;\n              background: white;\n              text-align: left;\n              cursor: pointer;\n            }\n            .dropdown-menu button:hover { background: #f7f9fa; }\n            .dropdown-menu button.active { background: #fafafe; }\n            .dropdown-menu button span:first-child { font-size: 18px; }\n            .dropdown-menu button div { display: flex; flex-direction: column; }\n            .dropdown-menu button b { font-size: 14px; }\n            .dropdown-menu button small { font-size: 11px; color: #657786; }\n            .timeline-count { font-size: 12px; color: #999; }\n      \n      `}&lt;\/style&gt;\n\n      {\/* \u2705 myapp56 - Hom,Local,Federated \ud0ed \ucd94\uac00 \/ Add Hom, Local, Federated Tabs *\/}\n      &lt;div className=\"timeline-dropdown-wrap\"&gt;\n        &lt;div className=\"timeline-dropdown\"&gt;\n          &lt;button className=\"dropdown-toggle\" onClick={()=&gt;setShowDropdown(!showDropdown)}&gt;\n            &lt;span&gt;{tab==='home'?'\ud83c\udfe0': tab==='local'?'\ud83c\udfd8':'\ud83c\udf0d'}&lt;\/span&gt;\n            &lt;span&gt;{tab}&lt;\/span&gt;\n            &lt;span&gt;{showDropdown?'\u25b2':'\u25bc'}&lt;\/span&gt;\n          &lt;\/button&gt;\n          {showDropdown &amp;&amp; (\n            &lt;div className=\"dropdown-menu\"&gt;\n              &lt;button className={tab==='home'?'active':''} onClick={()=&gt;{onTabChange('home'); setShowDropdown(false);}}&gt;\ud83c\udfe0 Home&lt;\/button&gt;\n              &lt;button className={tab==='local'?'active':''} onClick={()=&gt;{onTabChange('local'); setShowDropdown(false);}}&gt;\ud83c\udfd8 Local&lt;\/button&gt;\n              &lt;button className={tab==='federated'?'active':''} onClick={()=&gt;{onTabChange('federated'); setShowDropdown(false);}}&gt;\ud83c\udf0d Federated&lt;\/button&gt;\n            &lt;\/div&gt;\n          )}\n        &lt;\/div&gt;\n        &lt;div className=\"timeline-count\"&gt;{timeline.length} toots&lt;\/div&gt;\n      &lt;\/div&gt;\n\n      {timeline.map((p: any) =&gt; (\n        &lt;article key={`${p.source}-${p.id}`} className=\"pinafore-status\"&gt;\n          &lt;Avatar actor={p.actor} username={p.username} \/&gt;\n          &lt;div className=\"status-content\"&gt;\n            &lt;div className=\"status-header\"&gt;\n              &lt;b&gt;{getDisplayName(p.actor)}&lt;\/b&gt;\n              &lt;span className=\"status-time\"&gt;{new Date(p.created_at).toLocaleString()}&lt;\/span&gt;\n              {p.isBoostedPost &amp;&amp; &lt;span className=\"boost-label\"&gt;\ud83d\udd01 {getDisplayName(p.actor)}&lt;\/span&gt;}\n              {currentUser &amp;&amp; p.isMine &amp;&amp; (\n                &lt;button onClick={() =&gt; onDelete(p.id)} style={{ color: '#e0245e', marginLeft: 'auto' }}&gt;\n                  \ud83d\uddd1 Delete\n                &lt;\/button&gt;\n              )}\n            &lt;\/div&gt;\n            &lt;div className=\"status-text\" dangerouslySetInnerHTML={{ __html: p.content }} \/&gt;\n            {p.media_attachments &amp;&amp; p.media_attachments.length &gt; 0 &amp;&amp; (\n              &lt;div className={`status-media ${p.media_attachments.length === 1 ? 'single' : ''}`}&gt;\n                {p.media_attachments.map((m: any) =&gt; (\n                  &lt;img key={m.id} src={toAbsolute(m.preview_url || m.url)} alt=\"media\" loading=\"lazy\" \/&gt;\n                ))}\n              &lt;\/div&gt;\n            )}\n            &lt;div className=\"status-actions\"&gt;\n              &lt;button onClick={() =&gt; onBoost(p)} className={p.isMyBoost ? 'boosted' : ''}&gt;\ud83d\udd01 {p.boostCount || ''}&lt;\/button&gt;\n              &lt;button onClick={() =&gt; onLike(p)} className={p.isMyLike ? 'liked' : ''}&gt;\u2b50 {p.likeCount || ''}&lt;\/button&gt;\n              &lt;button onClick={() =&gt; onShare(p)}&gt;\u2708&lt;\/button&gt;\n              &lt;button&gt;\ud83d\udcac&lt;\/button&gt;\n            &lt;\/div&gt;\n          &lt;\/div&gt;\n        &lt;\/article&gt;\n      ))}\n    &lt;\/&gt;\n  );\n\n}<\/code><\/pre>\n\n\n\n<p>&#8212; app\/usersui\/[username]\/_components\/themes\/pinafore\/PinaforeTheme.tsx<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>  \n\/\/ \u2705 myapp56 - Home, Local, Federated \ud0ed \uc120\ud0dd \uc0c1\ud0dc \/ Home, Local, Federated tab selection state\n  const &#91;tab, setTab] = useState&lt;'home'|'local'|'federated'&gt;('home');\n... ...  \n\/\/ \u2705 myapp56 \n  \/\/ - Home \/ Local \/ Federated \ud0ed \ub85c\uc9c1 \n  \/\/ \/ Home \/ Local \/ Federated tab logic\n  useEffect(() =&gt; {\n    if (view !== 'timeline') return;\n    \/\/ \ud504\ub85c\ud544 \ud398\uc774\uc9c0\uba74 username \uae30\ubc18, \uc544\ub2c8\uba74 type \uae30\ubc18\n    \/\/ If it's a profile page, use username-based; otherwise, use type-based.\n    const url = tab === 'home' \n      ? `\/api\/timeline?username=${username}&amp;type=home`\n      : `\/api\/timeline?type=${tab}`;\n\n    fetch(url, { credentials: 'include', cache: 'no-store' })\n      .then(r =&gt; r.json())\n      .then(data =&gt; setTimeline(data))\n      .catch(e =&gt; console.error('tab fetch fail', e));\n  }, &#91;tab, username, view]);\n\n  return (\n    &lt;&gt; ... ...\n\n          {view === 'timeline' &amp;&amp; (\n            &lt;&gt;\n            {\/* \u2705 myapp56 - ADD Dropdown(Home\/Local\/Federated Tabs ) *\/}\n\n              &lt;TimelineList\n                timeline={timeline}\n                tab={tab}\n                onTabChange={setTab}\n                currentUser={currentUser}\n                onDelete={handleDelete}\n                onBoost={handleBoostClick}\n                onLike={handleLikeClick}\n                onShare={onShare}\n              \/&gt;\n            &lt;\/&gt;\n          )} {\/* end view === 'timeline' *\/}\n\n   ... ... &lt;\/&gt;\n\n );\n<\/code><\/pre>\n\n\n\n<p>\u2714\ufe0f  lib\/visibility.ts  &#8211; visibility \uaddc\uce59\uc5d0 \ub9de\uac8c \uc218\uc815 \/ Modify to comply with visibility rules.<\/p>\n\n\n\n<pre class=\"wp-block-code\" style=\"font-size:14px\"><code>export function canSee(viewer: string | null, post: any): boolean {\n  const vis = (post.visibility || 'public') as Visibility;\n  \n  if (vis === 'public') return true;\n  if (vis === 'unlisted') return true; \/\/ \ub204\uad6c\ub098 \ubcfc \uc218 \uc788\uc74c (\uc775\uba85 \ud3ec\ud568) \/ Anyone can see (including anonymous)\n\n  if (!viewer) return false; \/\/ private, direct\ub294 \ub85c\uadf8\uc778 \ud544\uc694 \/ Login required for private, direct\n\n  if (post.username === viewer) return true; \/\/ \ub0b4 \uae00\uc740 \ub2e4 \ubcf4\uc784 \/ I can see all my posts\n\n  if (vis === 'private') {\n    return isFollower(viewer, post.username); \/\/ \ud314\ub85c\uc6cc\ub9cc \/ Only followers can see\n  }\n  \n  if (vis === 'direct') {\n    \/\/ \uba58\uc158\ub41c \uc0ac\ub78c\ub9cc \/ Only mentioned users can see\n    const mentions = post.mentions || &#91;];\n    return mentions.includes(viewer);\n  }\n\n  return false;\n}<\/code><\/pre>\n\n\n\n<p>\ud83d\udcc1 \ud14c\uc2a4\ud2b8 \/ Test<\/p>\n\n\n\n<p>\u2714\ufe0f Hom,Local,Federated <\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"649\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-home-jpg.jpg\" alt=\"\" class=\"wp-image-11640\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-home-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-home-jpg-300x243.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-home-jpg-768x623.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-home-jpg-400x325.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Home<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"470\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-local-jpg.jpg\" alt=\"\" class=\"wp-image-11641\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-local-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-local-jpg-300x176.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-local-jpg-768x451.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-local-jpg-400x235.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Local<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"509\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-federated-jpg.jpg\" alt=\"\" class=\"wp-image-11642\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-federated-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-federated-jpg-300x191.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-federated-jpg-768x489.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-federated-jpg-400x255.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Federated<\/figcaption><\/figure>\n\n\n\n<p>\u2714\ufe0f \ub4dc\ub86d\ub2e4\uc6b4 \uba54\ub274 \/ Dropdown menu<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"513\" src=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-menu-jpg.jpg\" alt=\"\" class=\"wp-image-11643\" srcset=\"https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-menu-jpg.jpg 800w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-menu-jpg-300x192.jpg 300w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-menu-jpg-768x492.jpg 768w, https:\/\/www.freelifemakers.org\/wordpress\/wp-content\/uploads\/2026\/10\/myapp56-menu-jpg-400x257.jpg 400w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><figcaption class=\"wp-element-caption\">Menu<\/figcaption><\/figure>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\uc694\ud55c\ubcf5\uc74c 8\uc7a5 32\uc808 \/ John 8:32<\/p>\n\n\n\n<p>&#8220;\uadf8\ub9ac\uace0 \ub108\ud76c\ub294 \uc9c4\ub9ac\ub97c \uc54c\uac8c \ub420 \uac83\uc774\uba70, \uc9c4\ub9ac\uac00 \ub108\ud76c\ub97c \uc790\uc720\ub86d\uac8c \ud560 \uac83\uc774\ub2e4.&#8221;<\/p>\n\n\n\n<p>&#8220;Then you will know the truth ,and the truth will set you free&#8221;<\/p>\n<\/blockquote>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\ud83d\udc49\ud83c\udffb myapp56\uc5d0\uc11c\ub294 \ud0c0\uc784\ub77c\uc778 \uae00\ubcf4\uae30\ub97c Home \/ Local \/ Federated \ud0ed\uc73c\ub85c \ubcfc \uc218 \uc788\uc2b5\ub2c8\ub2e4.In myapp56, you can view the timeline under the Home, Local, and Federated tabs. \ud83d\udc49\ud83c\udffb \uc804\uccb4 \ucf54\ub4dc\ub294 \uae43\ud5c8\ube0c\uc5d0\uc11c \ud655\uc778 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.You can find the full code on GitHub. https:\/\/github.com\/gideonslife01\/flm-nextjs \ud83d\udcc1 \uc804\uccb4 \ud504\ub85c\uc81d\ud2b8 \uad6c\uc870 \/ Overall Project Structure \ud83d\udcc1 \ud504\ub85c\uc81d\ud2b8 \uc2dc\uc791 \/ Project [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[28,27,1,7],"tags":[],"class_list":["post-11601","post","type-post","status-publish","format-standard","hentry","category-news","category-nextjs","category-uncategorized","category-website","missing-thumbnail"],"_links":{"self":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/11601","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/comments?post=11601"}],"version-history":[{"count":43,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/11601\/revisions"}],"predecessor-version":[{"id":11650,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/posts\/11601\/revisions\/11650"}],"wp:attachment":[{"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/media?parent=11601"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/categories?post=11601"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.freelifemakers.org\/wordpress\/index.php\/wp-json\/wp\/v2\/tags?post=11601"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}