feat(notes): per-code private investment note panel

- New lib/notes.ts: localStorage key `notes:<code>` storing { text, updatedAt },
  2000-char cap, blank/whitespace removes the key.
- New InvestmentNote sidebar panel: textarea + character counter, save button
  enabled only when dirty, "방금 전 / N분 전 / 오늘 HH:MM / YYYY-MM-DD"
  relative timestamp on the last save, short save-confirmation toast.
- Mounted in [code]/page.tsx sidebar after AlertsPanel.

Also tightens writeTargets() to require strictly positive finite values,
matching the UI's parseNum() guard so the lib is safe under direct external
calls (reviewer's non-blocking note on a4a4a7f).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
claude-owner
2026-05-29 00:05:33 +09:00
parent a4a4a7f98c
commit 442cc38c32
4 changed files with 165 additions and 2 deletions

View File

@@ -24,9 +24,10 @@ export function readTargets(code: string): Targets {
export function writeTargets(code: string, t: Targets): void {
if (typeof window === "undefined") return;
// 둘 다 비어 있으면 키 자체를 지움 → 빈 객체 잔여물 제거.
// 양수 유한값만 통과. UI 가 이미 가드하지만 lib 자체도 방어 — 외부에서 직접 호출돼도 안전.
const cleaned: Targets = {};
if (t.target != null && Number.isFinite(t.target)) cleaned.target = t.target;
if (t.stop != null && Number.isFinite(t.stop)) cleaned.stop = t.stop;
if (t.target != null && Number.isFinite(t.target) && t.target > 0) cleaned.target = t.target;
if (t.stop != null && Number.isFinite(t.stop) && t.stop > 0) cleaned.stop = t.stop;
try {
if (cleaned.target == null && cleaned.stop == null) {
window.localStorage.removeItem(`${PREFIX}${code}`);