Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e78f280
fix: v1 어댑터 ID 승계를 index·같은 모드 우선으로 정렬
lee-sihun Aug 13, 2026
7786ae2
fix: 슬롯이 그대로인 키의 값 변경에서 ID 유지
lee-sihun Aug 13, 2026
c197f28
fix: v1 패치의 중복·타 컬렉션 id를 거부 대신 정화
lee-sihun Aug 13, 2026
030b036
fix: 레거시 전체 레코드 커밋 경계에서 요소 ID 백필
lee-sihun Aug 13, 2026
f6e6263
fix: 탭 프리셋이 안 준 컬렉션의 요소 ID를 회전시키지 않음
lee-sihun Aug 13, 2026
8e2621d
fix: 프리셋 트랜잭션의 한도 초과 관용을 자리 기준으로 판정
lee-sihun Aug 13, 2026
aa601de
fix: 분리 패널의 레이어 드롭 모델에서 plugin 행 누락
lee-sihun Aug 13, 2026
33979b3
fix: 백엔드 ID 재발급 시 선택이 무음으로 풀리던 문제
lee-sihun Aug 13, 2026
6132db2
fix: 붙여넣기 선택을 커밋 정산 전 동기 구간으로 되돌림
lee-sihun Aug 13, 2026
ba8be9f
fix: 붙여넣기가 복사본의 상대 스택 순서를 뒤집던 문제
lee-sihun Aug 13, 2026
ef22579
fix: 아이템 드롭의 그룹 판정을 live 모델로 재유도
lee-sihun Aug 13, 2026
bf6b367
fix: 레이어 드롭의 plugin z 쓰기를 fail-closed 게이트 뒤로
lee-sihun Aug 13, 2026
e054828
chore: 미사용 변수 정리 및 포맷팅
lee-sihun Aug 13, 2026
6a02997
docs: v1 쓰기의 중복 id 처리 동작 반영
lee-sihun Aug 13, 2026
b482c13
fix: index 승계를 형태 고정 경로로 한정하고 슬롯 웨이브를 값보다 앞으로
lee-sihun Aug 13, 2026
0eb7904
fix: 접힌 그룹 드롭 의도 보존과 plugin z 복원 정책 정리
lee-sihun Aug 13, 2026
6c20c1d
fix: 선택 재채택에 자리 내용 일치 조건 추가
lee-sihun Aug 13, 2026
bd8a507
fix: 프리셋 관용을 라벨·구조 위반까지 자리 기준으로 확장
lee-sihun Aug 13, 2026
65025c2
docs: 프리셋 ID 재발급 범위와 중복 처리 서술 정정
lee-sihun Aug 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/content/en/api-reference/editor/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@ that top-level collection. It is not an item-level diff.
Every element in the position collections (`keyPositions`, `statPositions`,
`graphPositions`, `knobPositions`) carries a stable `id` (UUID) assigned and
owned by the app. Treat it as opaque: echo back the value you read and never
invent or duplicate one. A written element with a missing or unknown `id`
gets a fresh one from the backend. See the
invent one. A written element with a missing or unknown `id` gets a fresh one
from the backend. If the same `id` appears more than once — for example when
you copy an element you read and append it — the element in its original slot
keeps the identity and the remaining copies get fresh ones. See the
[Keys API](/docs/api-reference/keys) for the identity rules.
</Callout>

Expand Down
11 changes: 7 additions & 4 deletions docs/content/en/api-reference/keys/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -296,10 +296,13 @@ interface ElementShadowSpec {

Every element position (`keyPositions`, `statPositions`, `graphPositions`,
`knobPositions`) carries a stable `id`. Treat it as opaque: echo back the value
you read, never invent one, and never copy an `id` onto another element. A
write whose `id` is missing or unknown gets a fresh one assigned by the
backend, and loading a preset re-issues every `id`. Use `id` to track an
element across reorders instead of its array index.
you read and never invent one. A write whose `id` is missing or unknown gets a
fresh one assigned by the backend. When the same `id` appears more than once —
for example when you copy an element you read and append it — the element in
its original slot keeps the identity and the remaining copies get fresh ones.
Loading a full preset re-issues every `id`; a tab preset re-issues only the
collections that preset supplies positions for. Use `id` to track an element
across reorders instead of its array index.

When a gradient field is present it takes priority over the matching solid
field, and the solid field is kept in sync with the first stop color on save.
Expand Down
7 changes: 4 additions & 3 deletions docs/content/ko/api-reference/editor/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ type EditorPatchV1 = {
위치 컬렉션(`keyPositions`, `statPositions`, `graphPositions`,
`knobPositions`)의 모든 요소는 앱이 발급·소유하는 안정 `id`(UUID)를
가집니다. 불투명 값으로 다루세요. 읽은 값을 그대로 되돌려 보내고, 직접
만들거나 중복시키지 마세요. `id`가 없거나 미확인인 요소를 쓰면 백엔드가 새
값을 발급합니다. 신원 규칙은 [Keys API](/docs/api-reference/keys)를
참고하세요.
만들지 마세요. `id`가 없거나 미확인인 요소를 쓰면 백엔드가 새 값을
발급합니다. 읽은 요소를 복사해 배열에 덧붙이는 경우처럼 같은 `id`가 여러 번
나오면, 원래 자리의 요소가 신원을 지키고 나머지 사본이 새 값을 받습니다.
신원 규칙은 [Keys API](/docs/api-reference/keys)를 참고하세요.
</Callout>

## 현재 문서 조회
Expand Down
10 changes: 6 additions & 4 deletions docs/content/ko/api-reference/keys/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,12 @@ interface ElementShadowSpec {

모든 요소 위치(`keyPositions`, `statPositions`, `graphPositions`,
`knobPositions`)는 안정 `id`를 가집니다. 불투명 값으로 다루세요. 읽은 값을
그대로 되돌려 보내고, 직접 만들거나 다른 요소에 복사하지 마세요. `id`가
없거나 미확인인 쓰기는 백엔드가 새 값을 발급하며, 프리셋을 불러오면 모든
`id`가 재발급됩니다. 재정렬을 가로질러 요소를 추적할 때는 배열 index 대신
`id`를 사용하세요.
그대로 되돌려 보내고, 직접 만들지 마세요. `id`가 없거나 미확인인 쓰기는
백엔드가 새 값을 발급합니다. 같은 `id`가 여러 번 나오면(읽은 요소를 복사해
덧붙이는 경우처럼) 원래 자리의 요소가 신원을 지키고 나머지 사본이 새 값을
받습니다. 전체 프리셋을 불러오면 모든 `id`가 재발급되고, 탭 프리셋은 그
프리셋이 위치를 준 컬렉션만 재발급됩니다. 재정렬을 가로질러 요소를 추적할
때는 배열 index 대신 `id`를 사용하세요.

그라데이션 필드가 있으면 렌더에서 대응 단색 필드보다 우선하며, 저장 시
대응 단색 필드는 첫 스톱 색으로 자동 동기화됩니다. 단색으로 되돌리려면
Expand Down
48 changes: 45 additions & 3 deletions src-tauri/src/commands/preset/load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,7 @@ pub fn preset_load_tab(
admission,
move |store| {
let previous_tab_css_overrides = store.tab_css_overrides.clone();
let key_positions_written = imported_key_positions.is_some();
let stat_positions_written = imported_stat_positions.is_some();
let graph_positions_written = imported_graph_positions.is_some();
let knob_positions_written = imported_knob_positions.is_some();
Expand All @@ -633,6 +634,7 @@ pub fn preset_load_tab(
rekey_tab_preset_elements(
store,
&current_tab_id,
key_positions_written,
stat_positions_written,
graph_positions_written,
knob_positions_written,
Expand Down Expand Up @@ -908,18 +910,29 @@ fn rekey_full_preset_elements(store: &mut AppStoreData) {
fn rekey_tab_preset_elements(
store: &mut AppStoreData,
tab_id: &str,
key_positions_written: bool,
stat_positions_written: bool,
graph_positions_written: bool,
knob_positions_written: bool,
) {
crate::state::native_element_id::rekey_mode_element_ids_for_collections(
store,
tab_id,
true,
key_positions_written,
stat_positions_written,
graph_positions_written,
knob_positions_written,
);
// 프리셋이 위치를 주지 않은 컬렉션은 기존 요소가 값 그대로 남는다 -
// 신원을 회전시키지 않고 정렬이 덧붙인 빈 항목만 채운다
crate::state::native_element_id::backfill_mode_element_ids_for_collections(
store,
tab_id,
!key_positions_written,
!stat_positions_written,
!graph_positions_written,
!knob_positions_written,
);
}

fn merge_tab_preset_key_pair(
Expand Down Expand Up @@ -2365,9 +2378,9 @@ mod tests {
let untouched_id = store.key_positions["untouched"][0].id.clone();
let original_ids = target_preset_ids(&store);

rekey_tab_preset_elements(&mut store, "target", true, true, false);
rekey_tab_preset_elements(&mut store, "target", true, true, true, false);
let first_ids = target_preset_ids(&store);
rekey_tab_preset_elements(&mut store, "target", true, true, false);
rekey_tab_preset_elements(&mut store, "target", true, true, true, false);
let second_ids = target_preset_ids(&store);

assert!(original_ids[..3]
Expand All @@ -2387,6 +2400,35 @@ mod tests {
.unwrap();
}

#[test]
fn tab_preset_without_key_positions_keeps_existing_key_ids() {
let mut store = old_preset_store();
crate::state::native_element_id::backfill_store_element_ids(&mut store);
let original_ids = target_preset_ids(&store);
// keys만 담긴 탭 프리셋: merge가 기존 위치를 값 그대로 되삽입하고
// 슬롯 정렬이 빈 위치 하나를 덧붙인 상태
store
.key_positions
.get_mut("target")
.unwrap()
.push(KeyPosition::default());

rekey_tab_preset_elements(&mut store, "target", false, false, false, false);

// 값이 그대로인 기존 키는 신원을 지키고, 덧붙은 슬롯만 새 id를 받는다
assert_eq!(store.key_positions["target"][0].id, original_ids[0]);
let appended = &store.key_positions["target"][1].id;
assert!(crate::state::native_element_id::is_valid_element_id(
appended
));
assert_ne!(appended, &original_ids[0]);
assert_eq!(target_preset_ids(&store)[1..], original_ids[1..]);
crate::state::native_element_id::validate_document_element_ids(
&crate::models::EditorDocumentV1::from_store(&store),
)
.unwrap();
}

#[test]
fn preset_import_alignment_repairs_each_mode_without_dropping_values() {
let mut keys = KeyMappings::from([
Expand Down
Loading