@ait-co/devtools 0.1.41 → 0.1.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +85 -0
- package/README.md +85 -0
- package/dist/mcp/cli.js +1355 -207
- package/dist/mcp/cli.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +94 -53
- package/dist/mcp/server.js.map +1 -1
- package/dist/mock/index.d.ts +0 -4
- package/dist/mock/index.d.ts.map +1 -1
- package/dist/mock/index.js +0 -1
- package/dist/mock/index.js.map +1 -1
- package/dist/panel/index.js +40 -52
- package/dist/panel/index.js.map +1 -1
- package/package.json +1 -1
package/dist/panel/index.js
CHANGED
|
@@ -528,7 +528,6 @@ const DEFAULT_STATE = {
|
|
|
528
528
|
preset: "none",
|
|
529
529
|
orientation: "auto",
|
|
530
530
|
appOrientation: null,
|
|
531
|
-
landscapeSide: "left",
|
|
532
531
|
customWidth: 402,
|
|
533
532
|
customHeight: 874,
|
|
534
533
|
frame: false,
|
|
@@ -1109,7 +1108,7 @@ function readGlobalString(key) {
|
|
|
1109
1108
|
}
|
|
1110
1109
|
const TELEMETRY_ENDPOINT = readGlobalString("__TELEMETRY_ENDPOINT__") ?? "https://t.aitc.dev";
|
|
1111
1110
|
function getVersion() {
|
|
1112
|
-
return "0.1.
|
|
1111
|
+
return "0.1.44";
|
|
1113
1112
|
}
|
|
1114
1113
|
let panelVisibleSince = null;
|
|
1115
1114
|
let accumulatedMs = 0;
|
|
@@ -3773,7 +3772,7 @@ function buildDeviceProfile(preset, appVersion, landscape) {
|
|
|
3773
3772
|
const tossToken = `AppsInToss TossApp/${appVersion}`;
|
|
3774
3773
|
const baseUa = platform === "ios" ? "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148" : "Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36";
|
|
3775
3774
|
const physWidth = Math.round(preset.width * preset.dpr);
|
|
3776
|
-
const physHeight = Math.round(preset.height * preset.dpr);
|
|
3775
|
+
const physHeight = Math.round((preset.screenHeight ?? preset.height) * preset.dpr);
|
|
3777
3776
|
return {
|
|
3778
3777
|
platform,
|
|
3779
3778
|
userAgent: `${baseUa} ${tossToken}`,
|
|
@@ -3843,8 +3842,10 @@ function syncDeviceEmulation(preset, landscape) {
|
|
|
3843
3842
|
*
|
|
3844
3843
|
* Panel에서 선택한 디바이스 프리셋을 `document.body`에 적용한다. 정적 CSS는
|
|
3845
3844
|
* `panel/styles.ts`에 정의되어 있고 (Panel mount 시 head에 주입), 여기서는 프리셋별
|
|
3846
|
-
* 동적 값(width/height
|
|
3847
|
-
*
|
|
3845
|
+
* 동적 값(width/height)만 별도 `<style>` 엘리먼트로 관리한다.
|
|
3846
|
+
*
|
|
3847
|
+
* body `padding-top`은 주입하지 않는다: 실기기에서 토스 native nav bar는 WebView viewport
|
|
3848
|
+
* 밖이라 콘텐츠는 top=0부터 시작한다(devtools#275).
|
|
3848
3849
|
*/
|
|
3849
3850
|
const VIEWPORT_STORAGE_KEY = "__ait_viewport";
|
|
3850
3851
|
/** Custom width/height의 안전 상한 (CSS px). 4K + 여유. */
|
|
@@ -3920,16 +3921,19 @@ const VIEWPORT_PRESETS = [
|
|
|
3920
3921
|
id: "iphone-15-pro",
|
|
3921
3922
|
label: "iPhone 15 Pro",
|
|
3922
3923
|
width: 393,
|
|
3923
|
-
height:
|
|
3924
|
+
height: 754,
|
|
3925
|
+
screenHeight: 852,
|
|
3924
3926
|
dpr: 3,
|
|
3925
3927
|
notch: "dynamic-island",
|
|
3926
3928
|
notchInset: 59,
|
|
3927
3929
|
navBarHeight: 54,
|
|
3928
3930
|
safeAreaBottom: 34,
|
|
3931
|
+
safeAreaBottomLandscape: 20,
|
|
3929
3932
|
safeAreaProvenance: {
|
|
3930
3933
|
source: "measured",
|
|
3931
3934
|
device: "iPhone 15 Pro",
|
|
3932
|
-
date: "2026-05-
|
|
3935
|
+
date: "2026-05-28",
|
|
3936
|
+
orientations: ["portrait", "landscape"]
|
|
3933
3937
|
}
|
|
3934
3938
|
},
|
|
3935
3939
|
{
|
|
@@ -4104,42 +4108,48 @@ function resolveViewportSize(state) {
|
|
|
4104
4108
|
}
|
|
4105
4109
|
/**
|
|
4106
4110
|
* 프리셋 + orientation + nav bar 상태로부터 SDK `SafeAreaInsets.get()`이 반환할 insets를
|
|
4107
|
-
* 계산한다. iPhone 15 Pro on-device relay 실측(devtools#190)에 맞춘 모델:
|
|
4111
|
+
* 계산한다. iPhone 15 Pro on-device relay 실측(devtools#190, #198, #232, #275)에 맞춘 모델:
|
|
4108
4112
|
*
|
|
4109
|
-
* - **Portrait top =
|
|
4110
|
-
*
|
|
4111
|
-
*
|
|
4112
|
-
*
|
|
4113
|
-
*
|
|
4114
|
-
*
|
|
4115
|
-
*
|
|
4116
|
-
*
|
|
4113
|
+
* - **Portrait top = 0** (partner/game 모두). 실측(devtools#275)에서 토스 native nav bar는
|
|
4114
|
+
* partner WebView **viewport 밖**에 그려진다. SDK가 반환하는 `top=54`는 호스트 nav bar
|
|
4115
|
+
* 높이에 대한 정보용 값이고, WebView 좌표계에서 콘텐츠는 top=0부터 시작한다. 소비자가
|
|
4116
|
+
* 이 값을 `padding-top`으로 적용하면 실기기에서 잉여 공간이 생긴다(double-count).
|
|
4117
|
+
* mock은 top=0을 반환해 소비자 코드가 실기기와 같은 결과를 내도록 한다.
|
|
4118
|
+
* `game` type 측정은 아직 미진행이지만 동일하게 top=0을 반환한다(추후 실측으로 갱신).
|
|
4119
|
+
* - **Bottom = `safeAreaBottom`** (portrait home-indicator, 실측 34).
|
|
4120
|
+
* landscape는 `safeAreaBottomLandscape`가 정의돼 있으면 그 값을 사용한다
|
|
4121
|
+
* (iPhone 15 Pro landscape 실측 20 — portrait 34와 다름).
|
|
4122
|
+
* - **Landscape iPhone(notch/Dynamic Island)**: CSS env()와 SDK SafeAreaInsets 모두
|
|
4123
|
+
* `left = right = notchInset`(양쪽 대칭)을 반환한다. 물리적 노치는 한쪽으로 가지만
|
|
4124
|
+
* OS가 양쪽 모두에 같은 inset을 부여하므로 landscapeSide mental model은 틀렸다
|
|
4125
|
+
* (2026-05-28 iPhone 15 Pro relay 실측 #198/#232: left=right=59). top=0(landscape에서
|
|
4126
|
+
* 토스 앱이 partner nav bar를 숨김, #232 실측 확인).
|
|
4117
4127
|
* - **Android punch-hole(status bar)**: landscape에서도 top에 status bar(`notchInset`)가
|
|
4118
4128
|
* 유지된다.
|
|
4119
4129
|
*/
|
|
4120
|
-
function computeSafeAreaInsets(preset, landscape
|
|
4130
|
+
function computeSafeAreaInsets(preset, landscape) {
|
|
4121
4131
|
if (preset.id === "none" || preset.id === "custom") return {
|
|
4122
4132
|
top: 0,
|
|
4123
4133
|
bottom: 0,
|
|
4124
4134
|
left: 0,
|
|
4125
4135
|
right: 0
|
|
4126
4136
|
};
|
|
4127
|
-
const navBarTop = navBarVisible && navBarType === "partner" ? preset.navBarHeight : 0;
|
|
4128
4137
|
if (!landscape) return {
|
|
4129
|
-
top:
|
|
4138
|
+
top: 0,
|
|
4130
4139
|
bottom: preset.safeAreaBottom,
|
|
4131
4140
|
left: 0,
|
|
4132
4141
|
right: 0
|
|
4133
4142
|
};
|
|
4143
|
+
const landscapeBottom = preset.safeAreaBottomLandscape !== void 0 ? preset.safeAreaBottomLandscape : preset.safeAreaBottom;
|
|
4134
4144
|
if (preset.notch === "notch" || preset.notch === "dynamic-island") return {
|
|
4135
4145
|
top: 0,
|
|
4136
|
-
bottom:
|
|
4137
|
-
left:
|
|
4138
|
-
right:
|
|
4146
|
+
bottom: landscapeBottom,
|
|
4147
|
+
left: preset.notchInset,
|
|
4148
|
+
right: preset.notchInset
|
|
4139
4149
|
};
|
|
4140
4150
|
return {
|
|
4141
4151
|
top: preset.notchInset,
|
|
4142
|
-
bottom:
|
|
4152
|
+
bottom: landscapeBottom,
|
|
4143
4153
|
left: 0,
|
|
4144
4154
|
right: 0
|
|
4145
4155
|
};
|
|
@@ -4147,7 +4157,7 @@ function computeSafeAreaInsets(preset, landscape, side, navBarVisible, navBarTyp
|
|
|
4147
4157
|
/** viewport preset 또는 orientation이 바뀌면 safe-area insets도 자동 갱신한다. */
|
|
4148
4158
|
function syncSafeAreaFromViewport(state) {
|
|
4149
4159
|
if (state.preset === "none" || state.preset === "custom") return;
|
|
4150
|
-
const next = computeSafeAreaInsets(getPreset(state.preset), effectiveOrientation(state) === "landscape"
|
|
4160
|
+
const next = computeSafeAreaInsets(getPreset(state.preset), effectiveOrientation(state) === "landscape");
|
|
4151
4161
|
const current = aitState.state.safeAreaInsets;
|
|
4152
4162
|
if (current.top === next.top && current.bottom === next.bottom && current.left === next.left && current.right === next.right) return;
|
|
4153
4163
|
aitState.update({ safeAreaInsets: next });
|
|
@@ -4188,12 +4198,10 @@ function removeNavBarElement() {
|
|
|
4188
4198
|
* 우측 `⋯` + 구분선 + `×`.
|
|
4189
4199
|
* - `game`: 투명 배경, 게임 캔버스를 가리지 않도록 우측 `⋯` + 구분선 + `×`만.
|
|
4190
4200
|
*
|
|
4191
|
-
*
|
|
4192
|
-
*
|
|
4193
|
-
*
|
|
4194
|
-
*
|
|
4195
|
-
* 바닥과 콘텐츠 시작이 정확히 맞물린다. 시각 notch 오버레이는 body 밖 위쪽(status bar
|
|
4196
|
-
* 영역)에 따로 그린다(`renderNotchOverlay`) — body 안이 아니다.
|
|
4201
|
+
* 이 오버레이는 **시각 참고용 frame 장식**이다. 실기기에서 토스 native nav bar는 WebView
|
|
4202
|
+
* viewport 밖에 그려지므로(devtools#275), mock의 nav bar 오버레이가 콘텐츠 위에 overlap
|
|
4203
|
+
* 되는 것이 실제 동작과 일치한다 — body에 `padding-top`을 주입하지 않는다.
|
|
4204
|
+
* 시각 notch 오버레이는 body 밖 위쪽에 따로 그린다(`renderNotchOverlay`) — body 안이 아니다.
|
|
4197
4205
|
*
|
|
4198
4206
|
* 뒤로가기 버튼은 `__ait:backEvent`를 트리거하고, X 버튼은 `closeView()`를 호출한다.
|
|
4199
4207
|
* 실제 SDK 이벤트 플러밍을 한 곳에서 검증할 수 있다.
|
|
@@ -4315,14 +4323,12 @@ function applyViewport(state) {
|
|
|
4315
4323
|
const preset = state.preset === "custom" ? null : getPreset(state.preset);
|
|
4316
4324
|
const landscape = effectiveOrientation(state) === "landscape";
|
|
4317
4325
|
syncDeviceEmulation(preset, landscape);
|
|
4318
|
-
const contentTop = preset ? computeSafeAreaInsets(preset, landscape, state.landscapeSide, state.aitNavBar, state.aitNavBarType).top : 0;
|
|
4319
4326
|
style.textContent = `
|
|
4320
4327
|
html.ait-viewport-active body {
|
|
4321
4328
|
width: ${size.width}px;
|
|
4322
4329
|
max-width: ${size.width}px;
|
|
4323
4330
|
min-height: ${size.height}px;
|
|
4324
4331
|
max-height: ${size.height}px;
|
|
4325
|
-
padding-top: ${contentTop}px;
|
|
4326
4332
|
}
|
|
4327
4333
|
`;
|
|
4328
4334
|
if (preset && state.frame && !landscape) renderNotchOverlay(preset);
|
|
@@ -4341,9 +4347,6 @@ function isViewportOrientation(v) {
|
|
|
4341
4347
|
function isAppOrientation(v) {
|
|
4342
4348
|
return v === null || v === "portrait" || v === "landscape";
|
|
4343
4349
|
}
|
|
4344
|
-
function isLandscapeSide(v) {
|
|
4345
|
-
return v === "left" || v === "right";
|
|
4346
|
-
}
|
|
4347
4350
|
/** 1 이상의 정수 + VIEWPORT_CUSTOM_MAX 이하인지 검사. sessionStorage 보호용. */
|
|
4348
4351
|
function isValidCustomDimension(v) {
|
|
4349
4352
|
return typeof v === "number" && Number.isInteger(v) && v >= 1 && v <= 4096;
|
|
@@ -4371,7 +4374,6 @@ function loadViewportFromStorage() {
|
|
|
4371
4374
|
if (isViewportPresetId(obj.preset)) next.preset = obj.preset;
|
|
4372
4375
|
if (isViewportOrientation(obj.orientation)) next.orientation = obj.orientation;
|
|
4373
4376
|
if (isAppOrientation(obj.appOrientation)) next.appOrientation = obj.appOrientation;
|
|
4374
|
-
if (isLandscapeSide(obj.landscapeSide)) next.landscapeSide = obj.landscapeSide;
|
|
4375
4377
|
if (isValidCustomDimension(obj.customWidth)) next.customWidth = obj.customWidth;
|
|
4376
4378
|
if (isValidCustomDimension(obj.customHeight)) next.customHeight = obj.customHeight;
|
|
4377
4379
|
if (typeof obj.frame === "boolean") next.frame = obj.frame;
|
|
@@ -4486,16 +4488,6 @@ function renderViewportTab() {
|
|
|
4486
4488
|
orientationSelect.addEventListener("change", () => {
|
|
4487
4489
|
aitState.patch("viewport", { orientation: orientationSelect.value });
|
|
4488
4490
|
});
|
|
4489
|
-
const landscapeSideSelect = h("select", { className: "ait-select" });
|
|
4490
|
-
if (disabled) landscapeSideSelect.disabled = true;
|
|
4491
|
-
for (const opt of ["left", "right"]) {
|
|
4492
|
-
const option = h("option", { value: opt }, opt);
|
|
4493
|
-
if (opt === vp.landscapeSide) option.selected = true;
|
|
4494
|
-
landscapeSideSelect.appendChild(option);
|
|
4495
|
-
}
|
|
4496
|
-
landscapeSideSelect.addEventListener("change", () => {
|
|
4497
|
-
aitState.patch("viewport", { landscapeSide: landscapeSideSelect.value });
|
|
4498
|
-
});
|
|
4499
4491
|
const customRow = h("div", { className: "ait-section" });
|
|
4500
4492
|
if (vp.preset === "custom") {
|
|
4501
4493
|
const widthInput = h("input", {
|
|
@@ -4566,7 +4558,7 @@ function renderViewportTab() {
|
|
|
4566
4558
|
const orientDisplay = vp.orientation === "auto" ? t("viewport.orientation.autoSuffix", { orient: effOrient }) : effOrient;
|
|
4567
4559
|
rows.push(h("div", { className: "ait-status-row" }, h("span", {}, t("viewport.status.cssPhysical")), h("span", { className: "ait-status-value" }, `${size.width}×${size.height}@${dpr}x | ${physW}×${physH} ${orientDisplay}`)));
|
|
4568
4560
|
if (preset) {
|
|
4569
|
-
const insets = computeSafeAreaInsets(preset, landscape
|
|
4561
|
+
const insets = computeSafeAreaInsets(preset, landscape);
|
|
4570
4562
|
const safeAreaValueEl = h("span", { className: "ait-status-value" }, `T${insets.top} R${insets.right} B${insets.bottom} L${insets.left}`);
|
|
4571
4563
|
const badge = provenanceBadge(preset.safeAreaProvenance);
|
|
4572
4564
|
if (badge) safeAreaValueEl.appendChild(badge);
|
|
@@ -4582,10 +4574,6 @@ function renderViewportTab() {
|
|
|
4582
4574
|
for (const row of rows) statusEl.appendChild(row);
|
|
4583
4575
|
}
|
|
4584
4576
|
const deviceSection = h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("viewport.section.device")), h("div", { className: "ait-row" }, h("label", {}, t("viewport.row.preset")), presetSelect), h("div", { className: "ait-row" }, h("label", {}, t("viewport.row.orientation")), orientationSelect));
|
|
4585
|
-
if (effectiveOrientation(vp) === "landscape" && vp.preset !== "none" && vp.preset !== "custom") {
|
|
4586
|
-
const notch = getPreset(vp.preset).notch;
|
|
4587
|
-
if (notch === "notch" || notch === "dynamic-island") deviceSection.appendChild(h("div", { className: "ait-row" }, h("label", {}, t("viewport.row.notchSide")), landscapeSideSelect));
|
|
4588
|
-
}
|
|
4589
4577
|
container.append(deviceSection, customRow, h("div", { className: "ait-section" }, h("div", { className: "ait-section-title" }, t("viewport.section.appearance")), h("div", { className: "ait-row" }, h("label", {}, t("viewport.row.showFrame")), frameCheckbox), h("div", { className: "ait-row" }, h("label", {}, t("viewport.row.showAitNavBar")), navBarCheckbox), h("div", { className: "ait-row" }, h("label", {}, t("viewport.row.navBarType")), navBarTypeSelect)), statusEl);
|
|
4590
4578
|
return container;
|
|
4591
4579
|
}
|
|
@@ -4873,7 +4861,7 @@ function mount() {
|
|
|
4873
4861
|
mockBadge.textContent = aitState.state.panelEditable ? t("panel.editMode.on") : t("panel.editMode.off");
|
|
4874
4862
|
refreshPanel();
|
|
4875
4863
|
});
|
|
4876
|
-
const headerRight = h("span", { style: "display:flex;align-items:center;gap:6px" }, mockBadge, h("span", { style: "font-size:11px;color:#666;font-weight:400" }, `v0.1.
|
|
4864
|
+
const headerRight = h("span", { style: "display:flex;align-items:center;gap:6px" }, mockBadge, h("span", { style: "font-size:11px;color:#666;font-weight:400" }, `v0.1.44`), closeBtn);
|
|
4877
4865
|
const header = h("div", { className: "ait-panel-header" }, h("span", {}, t("panel.title")), headerRight);
|
|
4878
4866
|
tabsEl = h("div", { className: "ait-panel-tabs" });
|
|
4879
4867
|
for (const tab of getTabs()) {
|