chore(release): v0.3.7 — 이동 modal currentStatus 필터 (Inbox 복원 path)
MoveStatusModal 이 완료/보관/휴지통 3 button hardcode 라 완료/보관/휴지통 노트가 inbox 로 돌아오는 path 가 없던 버그 fix. currentStatus prop 으로 4 status 중 current 제외 동적 render. '활성' label 도 헤더 탭과 일치하도록 'Inbox' 로 통일. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
19
CHANGELOG.md
19
CHANGELOG.md
@@ -3,6 +3,25 @@
|
||||
본 파일은 Inkling 의 버전별 사용자 영향 변경 사항을 기록한다.
|
||||
형식은 [Keep a Changelog](https://keepachangelog.com/) 를 느슨하게 따른다.
|
||||
|
||||
## [0.3.7] — 2026-05-11
|
||||
|
||||
`MoveStatusModal` 의 button hardcode 로 인해 완료/보관/휴지통 노트가 inbox 로 돌아올 수 없던 버그 fix. v0.2.9 Cut B 부터 존재한 잠재 결함 (dropdown 의 `possibleTargets` 필터가 modal 까지 흐르지 못함).
|
||||
|
||||
### 수정
|
||||
|
||||
- **완료/보관/휴지통 노트의 Inbox 복원 path 부재.** `MoveStatusModal` 이 `완료/보관/휴지통` 3 button hardcode 라 currentStatus 외 3 status 만 동적으로 노출해야 한다는 의도가 누락. `currentStatus: NoteStatus` prop 추가 + 4 status 중 current 제외 동적 render. NoteCard 가 `local.status` 전달.
|
||||
- **status label 일관성** — `statusLabel('active')` 가 '활성' 이었으나 헤더 탭 표기는 'Inbox'. modal button + AI 추천 텍스트 양쪽 모두 'Inbox' 로 통일.
|
||||
|
||||
### 게이트
|
||||
|
||||
- 단위 736 → **739 PASS** (+3: completed/archived/trashed currentStatus button list 검증)
|
||||
- typecheck 0 errors (src)
|
||||
- 신규 npm dependency 0
|
||||
|
||||
### 업그레이드
|
||||
|
||||
v0.3.6 인스톨러 위에 v0.3.7 인스톨러를 같은 위치에 실행하면 in-place 업그레이드. 데이터/마이그레이션 변경 없음.
|
||||
|
||||
## [0.3.6] — 2026-05-11
|
||||
|
||||
v0.3.5 의 이동 dropdown 단순화가 사용자 의도와 어긋난 점 정정. 이동 modal (사유 + AI 자동 분류 + 수동 status 선택) 은 보존해야 하는 핵심 UX 였음.
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "inkling",
|
||||
"version": "0.3.6",
|
||||
"version": "0.3.7",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "inkling",
|
||||
"version": "0.3.6",
|
||||
"version": "0.3.7",
|
||||
"dependencies": {
|
||||
"better-sqlite3": "12.9.0",
|
||||
"electron-log": "5.2.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "inkling",
|
||||
"version": "0.3.6",
|
||||
"version": "0.3.7",
|
||||
"private": true,
|
||||
"description": "Inkling — local-first 한 줄 보관 도구",
|
||||
"author": "altair823 <dlsrks0734@gmail.com>",
|
||||
|
||||
@@ -6,17 +6,24 @@ interface Props {
|
||||
noteId: string;
|
||||
rawText: string;
|
||||
summary: string;
|
||||
/** 현재 노트 status. 이 값을 제외한 나머지 status 가 이동 버튼으로 노출. */
|
||||
currentStatus: NoteStatus;
|
||||
onClose: () => void;
|
||||
onMoved: (status: NoteStatus, reason: string | null) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* v0.2.9 Cut B Task 7 — 메모 이동 Modal.
|
||||
* 메모 이동 Modal.
|
||||
*
|
||||
* 사유 입력 + 3 status 버튼 (완료/보관/휴지통) + AI 자동 분류.
|
||||
* 사유 입력 + AI 자동 분류 + 수동 status 선택. 버튼은 currentStatus 를 제외한
|
||||
* 나머지 status 만 노출 (v0.3.6 까지는 완료/보관/휴지통 hardcode 라 완료/보관 노트가
|
||||
* inbox 로 못 돌아오던 버그를 v0.3.7 에서 정정).
|
||||
*/
|
||||
const ALL_STATUSES: readonly NoteStatus[] = ['active', 'completed', 'archived', 'trashed'];
|
||||
|
||||
export function MoveStatusModal({
|
||||
noteId,
|
||||
currentStatus,
|
||||
onClose,
|
||||
onMoved
|
||||
}: Props): React.ReactElement {
|
||||
@@ -90,9 +97,11 @@ export function MoveStatusModal({
|
||||
<button onClick={() => void classify()} disabled={classifying}>
|
||||
{classifying ? '분류 중...' : 'AI 자동 분류'}
|
||||
</button>
|
||||
<button onClick={() => void move('completed')}>완료</button>
|
||||
<button onClick={() => void move('archived')}>보관</button>
|
||||
<button onClick={() => void move('trashed')}>휴지통</button>
|
||||
{ALL_STATUSES.filter((s) => s !== currentStatus).map((s) => (
|
||||
<button key={s} onClick={() => void move(s)}>
|
||||
{statusLabel(s)}
|
||||
</button>
|
||||
))}
|
||||
<button onClick={onClose} style={{ marginLeft: 'auto' }}>
|
||||
취소
|
||||
</button>
|
||||
@@ -126,7 +135,8 @@ export function MoveStatusModal({
|
||||
export function statusLabel(s: NoteStatus): string {
|
||||
switch (s) {
|
||||
case 'active':
|
||||
return '활성';
|
||||
// 헤더 탭 표기 ('Inbox') 와 일치. UI 전반에서 active = Inbox 동의어.
|
||||
return 'Inbox';
|
||||
case 'completed':
|
||||
return '완료';
|
||||
case 'archived':
|
||||
|
||||
@@ -468,6 +468,7 @@ export function NoteCard({ note, onDeleted, onUpdated, mode = 'inbox', onRestore
|
||||
noteId={local.id}
|
||||
rawText={local.rawText}
|
||||
summary={local.aiSummary ?? ''}
|
||||
currentStatus={local.status}
|
||||
onClose={() => setMoveOpen(false)}
|
||||
onMoved={(newStatus, reason) => {
|
||||
const updated = { ...local, status: newStatus, moveReason: reason };
|
||||
|
||||
@@ -32,6 +32,7 @@ describe('MoveStatusModal', () => {
|
||||
noteId="n1"
|
||||
rawText="t"
|
||||
summary=""
|
||||
currentStatus="active"
|
||||
onClose={vi.fn()}
|
||||
onMoved={vi.fn()}
|
||||
/>
|
||||
@@ -50,6 +51,7 @@ describe('MoveStatusModal', () => {
|
||||
noteId="n1"
|
||||
rawText="t"
|
||||
summary=""
|
||||
currentStatus="active"
|
||||
onClose={vi.fn()}
|
||||
onMoved={onMoved}
|
||||
/>
|
||||
@@ -69,6 +71,7 @@ describe('MoveStatusModal', () => {
|
||||
noteId="n1"
|
||||
rawText="t"
|
||||
summary=""
|
||||
currentStatus="active"
|
||||
onClose={vi.fn()}
|
||||
onMoved={onMoved}
|
||||
/>
|
||||
@@ -81,6 +84,59 @@ describe('MoveStatusModal', () => {
|
||||
await waitFor(() => expect(onMoved).toHaveBeenCalledWith('completed', '결재 끝'));
|
||||
});
|
||||
|
||||
it('currentStatus=completed → Inbox/보관/휴지통 노출, 완료 미노출', () => {
|
||||
render(
|
||||
<MoveStatusModal
|
||||
noteId="n1"
|
||||
rawText="t"
|
||||
summary=""
|
||||
currentStatus="completed"
|
||||
onClose={vi.fn()}
|
||||
onMoved={vi.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByRole('button', { name: 'Inbox' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '보관' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '휴지통' })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: '완료' })).toBeNull();
|
||||
});
|
||||
|
||||
it('currentStatus=archived → Inbox 버튼 클릭 시 setStatus("active") 호출', async () => {
|
||||
const onMoved = vi.fn();
|
||||
render(
|
||||
<MoveStatusModal
|
||||
noteId="n1"
|
||||
rawText="t"
|
||||
summary=""
|
||||
currentStatus="archived"
|
||||
onClose={vi.fn()}
|
||||
onMoved={onMoved}
|
||||
/>
|
||||
);
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Inbox' }));
|
||||
await waitFor(() => {
|
||||
expect(mockSetStatus).toHaveBeenCalledWith('n1', 'active', null);
|
||||
expect(onMoved).toHaveBeenCalledWith('active', null);
|
||||
});
|
||||
});
|
||||
|
||||
it('currentStatus=trashed → Inbox/완료/보관 노출, 휴지통 미노출', () => {
|
||||
render(
|
||||
<MoveStatusModal
|
||||
noteId="n1"
|
||||
rawText="t"
|
||||
summary=""
|
||||
currentStatus="trashed"
|
||||
onClose={vi.fn()}
|
||||
onMoved={vi.fn()}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByRole('button', { name: 'Inbox' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '완료' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '보관' })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: '휴지통' })).toBeNull();
|
||||
});
|
||||
|
||||
it('빈 사유 → null reason 전달', async () => {
|
||||
const onMoved = vi.fn();
|
||||
render(
|
||||
@@ -88,6 +144,7 @@ describe('MoveStatusModal', () => {
|
||||
noteId="n1"
|
||||
rawText="t"
|
||||
summary=""
|
||||
currentStatus="active"
|
||||
onClose={vi.fn()}
|
||||
onMoved={onMoved}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user