Compare commits

..

2 Commits

Author SHA1 Message Date
posimai b5b721cd60 fix(security): add SRI to xterm CDN, add manifest id, noreferrer to target=_blank
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 18:42:38 +09:00
posimai 147d85abf6 feat(together): cursor-based pagination API — limit/cursor クエリパラメーター追加 2026-04-17 18:34:06 +09:00
5 changed files with 32 additions and 18 deletions

View File

@ -26,10 +26,10 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://posimai-ui.vercel.app/v1/base.css"> <link rel="stylesheet" href="https://posimai-ui.vercel.app/v1/base.css">
<link rel="stylesheet" href="https://unpkg.com/@xterm/xterm@6.0.0/css/xterm.css"> <link rel="stylesheet" href="https://unpkg.com/@xterm/xterm@6.0.0/css/xterm.css" integrity="sha384-n2n7twoohnW+d3myBKaUgl7DSiwidw6MkQy9oesGzkPpMjejKRR3XlnD+5yCdtBD" crossorigin="anonymous">
<script src="https://unpkg.com/lucide@0.344.0/dist/umd/lucide.min.js" integrity="sha384-tTkFttkBclaU1cloKwOi9xk3pbao3VZxTjLNBt8iFABWDBQibbAbWpVmO28zMuxq" crossorigin="anonymous"></script> <script src="https://unpkg.com/lucide@0.344.0/dist/umd/lucide.min.js" integrity="sha384-tTkFttkBclaU1cloKwOi9xk3pbao3VZxTjLNBt8iFABWDBQibbAbWpVmO28zMuxq" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@xterm/xterm@6.0.0/lib/xterm.js"></script> <script src="https://unpkg.com/@xterm/xterm@6.0.0/lib/xterm.js" integrity="sha384-f/1U6Z9wM4D71a5eRXEZnyOTMOvjqxr2XLwh+Go1OvIl3L3tOcvUrzudnhbECwl4" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@xterm/addon-fit@0.11.0/lib/addon-fit.js"></script> <script src="https://unpkg.com/@xterm/addon-fit@0.11.0/lib/addon-fit.js" integrity="sha384-txoiwu4RR2GD3qySbaj+BbzibkLbSJRcfqGYMu6z1EqHil4A2dyBiBW5dlacG6OR" crossorigin="anonymous"></script>
<style> <style>
:root { :root {
@ -294,7 +294,7 @@
<i data-lucide="terminal" style="width:12px;height:12px;stroke-width:1.75;color:var(--accent);flex-shrink:0"></i> <i data-lucide="terminal" style="width:12px;height:12px;stroke-width:1.75;color:var(--accent);flex-shrink:0"></i>
<span id="settingsSessionId"></span> <span id="settingsSessionId"></span>
</div> </div>
<a href="/sessions.html" style="font-size:13px;color:var(--accent);text-decoration:none;display:flex;align-items:center;gap:6px;margin-top:10px" rel="noopener"> <a href="/sessions.html" style="font-size:13px;color:var(--accent);text-decoration:none;display:flex;align-items:center;gap:6px;margin-top:10px" rel="noopener noreferrer">
<i data-lucide="history" style="width:14px;height:14px;stroke-width:1.75"></i> <i data-lucide="history" style="width:14px;height:14px;stroke-width:1.75"></i>
過去のセッションを見る 過去のセッションを見る
</a> </a>

View File

@ -1,4 +1,5 @@
{ {
"id": "/posimai-dev/",
"name": "posimai-dev", "name": "posimai-dev",
"short_name": "dev", "short_name": "dev",
"description": "posimai development portal", "description": "posimai development portal",

View File

@ -227,7 +227,7 @@
<div class="stat-card"><div class="stat-label">Node.js</div><div class="stat-val" id="node-val"></div></div> <div class="stat-card"><div class="stat-label">Node.js</div><div class="stat-val" id="node-val"></div></div>
<div class="stat-card"><div class="stat-label">Platform</div><div class="stat-val" id="platform-val"></div></div> <div class="stat-card"><div class="stat-label">Platform</div><div class="stat-val" id="platform-val"></div></div>
</div> </div>
<a class="open-btn" href="/" target="_blank" rel="noopener"><i data-lucide="terminal"></i>posimai-dev を開く</a> <a class="open-btn" href="/" target="_blank" rel="noopener noreferrer"><i data-lucide="terminal"></i>posimai-dev を開く</a>
<!-- Divider --> <!-- Divider -->
<div class="machines-divider"></div> <div class="machines-divider"></div>
<!-- VPS section --> <!-- VPS section -->
@ -333,10 +333,10 @@
<div id="bottom-row"> <div id="bottom-row">
<div class="bottom-brand">posimai<span>-station</span> <span style="font-size:10px;color:var(--violet);margin-left:4px">B</span></div> <div class="bottom-brand">posimai<span>-station</span> <span style="font-size:10px;color:var(--violet);margin-left:4px">B</span></div>
<div class="bottom-links"> <div class="bottom-links">
<a class="bottom-link" href="/station" rel="noopener"><i data-lucide="monitor"></i>Design A</a> <a class="bottom-link" href="/station" rel="noopener noreferrer"><i data-lucide="monitor"></i>Design A</a>
<a class="bottom-link" href="/" target="_blank" rel="noopener"><i data-lucide="terminal"></i>dev</a> <a class="bottom-link" href="/" target="_blank" rel="noopener noreferrer"><i data-lucide="terminal"></i>dev</a>
<a class="bottom-link" href="https://posimai-atlas.vercel.app" target="_blank" rel="noopener"><i data-lucide="network"></i>atlas</a> <a class="bottom-link" href="https://posimai-atlas.vercel.app" target="_blank" rel="noopener noreferrer"><i data-lucide="network"></i>atlas</a>
<a class="bottom-link" href="https://posimai.soar-enrich.com" target="_blank" rel="noopener"><i data-lucide="layout-dashboard"></i>dashboard</a> <a class="bottom-link" href="https://posimai.soar-enrich.com" target="_blank" rel="noopener noreferrer"><i data-lucide="layout-dashboard"></i>dashboard</a>
</div> </div>
<div id="refresh-countdown">次の更新まで <span id="countdown">30</span>s</div> <div id="refresh-countdown">次の更新まで <span id="countdown">30</span>s</div>
</div> </div>

View File

@ -234,7 +234,7 @@
<div class="stat-card"><div class="stat-label">Platform</div><div class="stat-val" id="platform-val"></div></div> <div class="stat-card"><div class="stat-label">Platform</div><div class="stat-val" id="platform-val"></div></div>
<div class="stat-card" id="temp-card" style="display:none"><div class="stat-label">CPU Temp</div><div class="stat-val" id="temp-val"></div></div> <div class="stat-card" id="temp-card" style="display:none"><div class="stat-label">CPU Temp</div><div class="stat-val" id="temp-val"></div></div>
</div> </div>
<a class="open-btn" href="/" target="_blank" rel="noopener"><i data-lucide="terminal"></i>posimai-dev を開く</a> <a class="open-btn" href="/" target="_blank" rel="noopener noreferrer"><i data-lucide="terminal"></i>posimai-dev を開く</a>
</div> </div>
<!-- Col 2: rings + sparkline --> <!-- Col 2: rings + sparkline -->
<div class="panel rings-panel"> <div class="panel rings-panel">
@ -317,10 +317,10 @@
<div id="bottom-row"> <div id="bottom-row">
<div class="bottom-brand">posimai<span>-station</span></div> <div class="bottom-brand">posimai<span>-station</span></div>
<div class="bottom-links"> <div class="bottom-links">
<a class="bottom-link" href="/station-b" rel="noopener"><i data-lucide="monitor"></i>Design B</a> <a class="bottom-link" href="/station-b" rel="noopener noreferrer"><i data-lucide="monitor"></i>Design B</a>
<a class="bottom-link" href="/" target="_blank" rel="noopener"><i data-lucide="terminal"></i>dev</a> <a class="bottom-link" href="/" target="_blank" rel="noopener noreferrer"><i data-lucide="terminal"></i>dev</a>
<a class="bottom-link" href="https://posimai-atlas.vercel.app" target="_blank" rel="noopener"><i data-lucide="network"></i>atlas</a> <a class="bottom-link" href="https://posimai-atlas.vercel.app" target="_blank" rel="noopener noreferrer"><i data-lucide="network"></i>atlas</a>
<a class="bottom-link" href="https://posimai.soar-enrich.com" target="_blank" rel="noopener"><i data-lucide="layout-dashboard"></i>dashboard</a> <a class="bottom-link" href="https://posimai.soar-enrich.com" target="_blank" rel="noopener noreferrer"><i data-lucide="layout-dashboard"></i>dashboard</a>
</div> </div>
<div id="refresh-countdown">次の更新まで <span id="countdown">30</span>s</div> <div id="refresh-countdown">次の更新まで <span id="countdown">30</span>s</div>
</div> </div>

View File

@ -2547,8 +2547,17 @@ ${excerpt}
}); });
// GET /together/feed/:groupId — フィード(リアクション・コメント数付き) // GET /together/feed/:groupId — フィード(リアクション・コメント数付き)
// ?limit=N&cursor=<ISO timestamp> でカーソルページネーション対応
r.get('/together/feed/:groupId', async (req, res) => { r.get('/together/feed/:groupId', async (req, res) => {
try { try {
const limit = Math.min(parseInt(req.query.limit) || 20, 50);
const cursor = req.query.cursor; // shared_at の ISO タイムスタンプ
const params = [req.params.groupId];
let cursorClause = '';
if (cursor) {
params.push(cursor);
cursorClause = 'AND s.shared_at < $2';
}
const result = await pool.query(` const result = await pool.query(`
SELECT SELECT
s.*, s.*,
@ -2560,12 +2569,16 @@ ${excerpt}
FROM together_shares s FROM together_shares s
LEFT JOIN together_reactions r ON r.share_id = s.id LEFT JOIN together_reactions r ON r.share_id = s.id
LEFT JOIN together_comments c ON c.share_id = s.id LEFT JOIN together_comments c ON c.share_id = s.id
WHERE s.group_id = $1 WHERE s.group_id = $1 ${cursorClause}
GROUP BY s.id GROUP BY s.id
ORDER BY s.shared_at DESC ORDER BY s.shared_at DESC
LIMIT 50 LIMIT ${limit + 1}
`, [req.params.groupId]); `, params);
res.json(result.rows); const rows = result.rows;
const hasMore = rows.length > limit;
const items = hasMore ? rows.slice(0, limit) : rows;
const nextCursor = items.length > 0 ? items[items.length - 1].shared_at : null;
res.json({ items, next_cursor: nextCursor, has_more: hasMore });
} catch (e) { } catch (e) {
console.error('[together/feed]', e.message); console.error('[together/feed]', e.message);
res.status(500).json({ error: 'Internal server error' }); res.status(500).json({ error: 'Internal server error' });