diff --git a/dist.zip b/dist.zip index c354b2c..0292a4a 100644 Binary files a/dist.zip and b/dist.zip differ diff --git a/dist/manifest.json b/dist/manifest.json index cc6e51f..b7ccbc8 100644 --- a/dist/manifest.json +++ b/dist/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "voim", - "version": "1.0.0", + "version": "2.0.0", "description": "시각 정보 해독이 어려운 사용자를 위한 크롬 확장 프로그램", "action": { "default_icon": { @@ -46,14 +46,14 @@ "128": "icons/icon-128.png" }, "host_permissions": ["", "https://voim.store/*"], - "permissions": ["scripting", "activeTab", "storage", "tabs", "sidePanel"], + "permissions": ["scripting", "activeTab", "storage", "tabs"], "commands": { "toggle_iframe": { "suggested_key": { "default": "Alt+V", "mac": "Alt+V" }, - "description": "Toggle floating iframe" + "description": "아이콘 끄기/키기" }, "toggle_modal": { "suggested_key": { @@ -74,7 +74,7 @@ "default": "Alt+A", "mac": "Alt+A" }, - "description": "모든 기능 토글" + "description": "모든 기능 끄기/키기" } } } diff --git a/public/manifest.json b/public/manifest.json index cc6e51f..b7ccbc8 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "voim", - "version": "1.0.0", + "version": "2.0.0", "description": "시각 정보 해독이 어려운 사용자를 위한 크롬 확장 프로그램", "action": { "default_icon": { @@ -46,14 +46,14 @@ "128": "icons/icon-128.png" }, "host_permissions": ["", "https://voim.store/*"], - "permissions": ["scripting", "activeTab", "storage", "tabs", "sidePanel"], + "permissions": ["scripting", "activeTab", "storage", "tabs"], "commands": { "toggle_iframe": { "suggested_key": { "default": "Alt+V", "mac": "Alt+V" }, - "description": "Toggle floating iframe" + "description": "아이콘 끄기/키기" }, "toggle_modal": { "suggested_key": { @@ -74,7 +74,7 @@ "default": "Alt+A", "mac": "Alt+A" }, - "description": "모든 기능 토글" + "description": "모든 기능 끄기/키기" } } } diff --git a/src/assets/onboardingData.ts b/src/assets/onboardingData.ts index 60b05b6..ea6ee81 100644 --- a/src/assets/onboardingData.ts +++ b/src/assets/onboardingData.ts @@ -2,29 +2,28 @@ export const onboardingData = { info: { title: "더 나은 쇼핑 경험을 위한 VOIM", phrase: [ - "VOIM은 쇼핑 시 이미지 속 제품 정보를 자동으로 요약하고, 중요한 성분을 분석해 전달하는 시각 보조 장치입니다.", - "시각 편의 설정을 위해 메뉴바를 켜시려면 우측 상단의 VOIM 아이콘을 클릭하거나 단축키 ALT + O를 눌러주세요.\n서비스 아이콘 숨김 설정은 메뉴 > 서비스 설정에서 가능합니다.", - "하단의 다음 버튼을 누르시면 서비스의 기능에 대한 설명이 나옵니다. 안내를 건너뛰시려면 우측 상단의 닫기 버튼을 눌러주세요.", + "보임은 온라인 쇼핑 시 이미지 속 정보를 자동으로 요약해 상품의\n중요한 성분 정보를 전달하는 시각 보조 장치입니다.\n", + + "서비스 기능 설명을 보려면 다음 버튼을, 설명을 건너뛰고 서비스를\n즉시 이용하려면 닫기 버튼을 눌러주세요.", ], }, image: { title: "이미지 분석 기능 안내", phrase: [ - "이미지 정보를 알아보고 싶을 경우, 이미지 좌측 상단에 있는 이미지 분석 버튼을 클릭하면 선택한 이미지의 정보를 텍스트로 제공받을 수 있습니다.", - "쿠팡의 경우, 상품 상세 페이지에 진입하면 VOIM이 자동으로 작동되어 상품 관련 요약 정보를 화면에 제공합니다.", + "이미지의 정보를 텍스트로 제공받으려면 이미지 좌측 상단에 있는\n이미지 분석 버튼을 눌러주세요.\n", + "쿠팡 이용 시, 상품 상세 페이지에서 상품 요약 정보가 자동\n제공됩니다.", ], }, product: { title: "성분 분석 기능 안내", phrase: [ - "식품, 화장품, 건강기능식품 상품에 한하여 성분과 관련된 주의사항을 강조해 화면에 제공합니다.", + "식품, 화장품, 건강기능식품 상품에 한하여 성분 관련 주의사항이\n제공됩니다.", ], }, theme: { title: "고대비모드 ∙ 글자 ∙ 마우스 커서 설정 안내", phrase: [ - "화면 우측 상단의 서비스 아이콘을 클릭하거나 단축키 ALT +O를 눌러 시각 편의 설정을 위한 메뉴바를 켤 수 있습니다.", - "서비스 사용에 필요한 모든 단축키에 대한 안내도 제공하고 있으니 메뉴바를 참고해주시기 바랍니다.", + "시각 편의 설정을 켜려면 서비스 아이콘을 누르거나\n단축키 ALT + O를 눌러주세요.", ], }, }; diff --git a/src/background/index.ts b/src/background/index.ts index 38d6348..72e79f4 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -146,8 +146,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { body: JSON.stringify(payload), }).then(async (res) => { const text = await res.text(); - console.log("[voim] 응답 상태 코드:", res.status); - console.log("[voim] 응답 원문:", text); + try { const json = JSON.parse(text); if (res.ok) { @@ -282,7 +281,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { }) .then(async (res) => { const json = await res.json(); - console.log("[voim][background] 응답 원문:", json); + return json; }) .then((data) => { diff --git a/src/components/panelContent/component.tsx b/src/components/panelContent/component.tsx index f4b54cc..ca9467e 100644 --- a/src/components/panelContent/component.tsx +++ b/src/components/panelContent/component.tsx @@ -1,11 +1,11 @@ import React, { useRef } from "react"; import { useFocusManagement } from "@src/hooks/useFocusManagement"; -import ControlMode from "@src/components/modeButton/ControlMode"; -import ControlFont from "@src/components/fontButton/ControlFont"; -import { ShortcutTab } from "@src/components/shortcutTab"; import { MyInfo } from "@src/tabs/myInfo"; -import ControlService from "@src/components/serviceButton/ControlService"; import { menuItems } from "@src/constants/menuItems"; +import ControlMode from "@src/tabs/modeButton/ControlMode"; +import ControlFont from "@src/tabs/fontButton/ControlFont"; +import { ShortcutTab } from "@src/tabs/shortcutTab"; +import ControlService from "@src/tabs/serviceButton/ControlService"; interface PanelContentProps { menuId: string | null; @@ -29,7 +29,7 @@ export const PanelContent: React.FC = ({ case "shortcut": return ; case "my-info": - return ; + return setMenuId(null)} />; case "service": return setMenuId(null)} />; default: diff --git a/src/components/productComponents/cosmeticComponent.tsx b/src/components/productComponents/cosmeticComponent.tsx index cb25e12..6423b04 100644 --- a/src/components/productComponents/cosmeticComponent.tsx +++ b/src/components/productComponents/cosmeticComponent.tsx @@ -164,6 +164,7 @@ export const CosmeticComponent = () => { padding: "16px", marginTop: "12px", borderRadius: "12px", + width: "100%", }} > {detectedIngredients.map((item, idx) => ( diff --git a/src/components/productComponents/foodComponent.tsx b/src/components/productComponents/foodComponent.tsx index 8c4af75..52588ee 100644 --- a/src/components/productComponents/foodComponent.tsx +++ b/src/components/productComponents/foodComponent.tsx @@ -45,7 +45,6 @@ const allergyNameMap: Record = { }; export const FoodComponent = () => { - console.log("FoodComponent 등장"); const [nutrientAlerts, setNutrientAlerts] = useState( null, ); @@ -122,10 +121,6 @@ export const FoodComponent = () => { retryRes?.html?.trim() && retryRes?.productId ) { - console.log( - "[voim] FETCH_VENDOR_HTML 재시도 성공:", - retryRes, - ); clearInterval(interval); resolve(retryRes); } else if (--retries === 0) { @@ -140,10 +135,6 @@ export const FoodComponent = () => { ); }, 500); } else { - console.log( - "[voim] FETCH_VENDOR_HTML 성공 응답:", - res, - ); resolve(res); } }, @@ -159,12 +150,8 @@ export const FoodComponent = () => { allergies: Allergies || [], }; - console.log("[voim] FOOD API 요청 payload:", payload); - const result = await sendFoodDataRequest(payload); - console.log("[voim] FOOD API 응답:", result); - setNutrientAlerts(result.overRecommendationNutrients || []); setAllergyTypes(result.allergyTypes || []); } catch (e) { diff --git a/src/components/productComponents/healthComponent.tsx b/src/components/productComponents/healthComponent.tsx index 8d9634e..5a00ec5 100644 --- a/src/components/productComponents/healthComponent.tsx +++ b/src/components/productComponents/healthComponent.tsx @@ -126,10 +126,8 @@ export const HealthComponent = () => { allergies: Allergies || [], }; - console.log("[voim] HEALTH API 요청 payload:", payload); - const result = await sendHealthDataRequest(payload); - console.log("[voim] HEALTH API 응답:", result); + setHealthTypes(result || []); } catch (e) { console.error("[voim] HEALTH API 실패:", e); diff --git a/src/components/sizeController/components.tsx b/src/components/sizeController/components.tsx deleted file mode 100644 index 942372e..0000000 --- a/src/components/sizeController/components.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { getExtensionUrl } from "@src/utils/getExtensionUrl"; -import React from "react"; - -interface SizeControllerProps { - type: "minus" | "plus"; -} - -export function SizeController({ type }: SizeControllerProps): JSX.Element { - return ( -
- {type} -
- ); -} diff --git a/src/components/sizeController/index.ts b/src/components/sizeController/index.ts deleted file mode 100644 index fdb15a6..0000000 --- a/src/components/sizeController/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { SizeController } from "./components"; diff --git a/src/content/apiSetting/sendCosmeticDataRequest.tsx b/src/content/apiSetting/sendCosmeticDataRequest.tsx index 10574a5..d05459f 100644 --- a/src/content/apiSetting/sendCosmeticDataRequest.tsx +++ b/src/content/apiSetting/sendCosmeticDataRequest.tsx @@ -4,9 +4,9 @@ interface CosmeticRequestPayload { } interface CosmeticAPIResponse { - status: number; - message?: string; - data: Record; + type: string; + data?: Record; + error?: string; } export const sendCosmeticDataRequest = ( @@ -19,22 +19,20 @@ export const sendCosmeticDataRequest = ( payload, }, (response: unknown) => { + console.log("[voim] ⬅️ 응답 확인:", response); if (!response || typeof response !== "object") { return reject(new Error("Invalid response format")); } const res = response as CosmeticAPIResponse; - if (res.status !== 200) { - return reject(new Error(`API 실패 status: ${res.status}`)); + if (!res.data || typeof res.data !== "object") { + return reject( + new Error("data 필드가 없거나 잘못된 형식입니다."), + ); } - const data = res.data; - if (!data || typeof data !== "object") { - return reject(new Error("올바르지 않은 응답 형식")); - } - - const detected = Object.entries(data) + const detected = Object.entries(res.data) .filter(([_, v]) => v === true) .map(([k]) => k); diff --git a/src/content/apiSetting/sendHealthDataRequest.tsx b/src/content/apiSetting/sendHealthDataRequest.tsx index 927c182..3e93658 100644 --- a/src/content/apiSetting/sendHealthDataRequest.tsx +++ b/src/content/apiSetting/sendHealthDataRequest.tsx @@ -15,8 +15,6 @@ export const sendHealthDataRequest = ( payload: HealthRequestPayload, ): Promise => { return new Promise((resolve, reject) => { - console.log("[voim] HEALTH API 요청 payload:", payload); - chrome.runtime.sendMessage( { type: "FETCH_HEALTH_DATA", @@ -38,10 +36,6 @@ export const sendHealthDataRequest = ( }; if (res.type === "HEALTH_DATA_RESPONSE" && res.data?.types) { - console.log( - "[voim] HEALTH API 응답 데이터:", - res.data.types, - ); resolve(res.data.types); } else { console.error( diff --git a/src/content/apiSetting/sendReviewSummaryRequest.tsx b/src/content/apiSetting/sendReviewSummaryRequest.tsx index d03c17a..acbf9b7 100644 --- a/src/content/apiSetting/sendReviewSummaryRequest.tsx +++ b/src/content/apiSetting/sendReviewSummaryRequest.tsx @@ -60,34 +60,20 @@ const waitForElement = ( export const collectCoupangReviewData = async (): Promise => { try { - console.log("[voim] 리뷰 데이터 수집 시작"); - // 첫 번째 케이스: .review-star-search-selector let starRatingContainer = await waitForElement( ".review-star-search-selector", ); - console.log( - "[voim] 첫 번째 케이스 별점 컨테이너:", - starRatingContainer, - ); // 두 번째 케이스: .sdp-review__article__order__star__option if (!starRatingContainer) { starRatingContainer = await waitForElement( ".sdp-review__article__order__star__option", ); - console.log( - "[voim] 두 번째 케이스 별점 컨테이너:", - starRatingContainer, - ); } if (!starRatingContainer) { console.error("[voim] 별점 컨테이너를 찾을 수 없습니다."); - console.log( - "[voim] 현재 페이지 HTML:", - document.body.innerHTML, - ); return null; } @@ -99,14 +85,12 @@ export const collectCoupangReviewData = ) ?.textContent?.trim() || "0"; const totalCount = parseInt(totalCountText.replace(/,/g, "")); - console.log("[voim] 전체 리뷰 수:", totalCount); // 별점별 개수 수집 const ratingsArray: number[] = [0, 0, 0, 0, 0]; // [최고, 좋음, 보통, 별로, 나쁨] const starItems = starRatingContainer.querySelectorAll( ".review-star-search-item, .sdp-review__article__order__star__list__item", ); - console.log("[voim] 별점 항목 수:", starItems.length); starItems.forEach((item) => { const ratingText = item @@ -121,13 +105,6 @@ export const collectCoupangReviewData = ) ?.textContent?.trim() || "0"; - console.log( - "[voim] 별점 텍스트:", - ratingText, - "개수:", - countText, - ); - const count = parseInt(countText.replace(/,/g, "")); if (ratingText === "최고") ratingsArray[0] = count; else if (ratingText === "좋음") ratingsArray[1] = count; @@ -136,13 +113,10 @@ export const collectCoupangReviewData = else if (ratingText === "나쁨") ratingsArray[4] = count; }); - console.log("[voim] 별점 배열:", ratingsArray); - // 리뷰 텍스트 수집 const reviewElements = document.querySelectorAll( ".review-content, .sdp-review__article__list__review__content", ); - console.log("[voim] 리뷰 요소 수:", reviewElements.length); const reviews: string[] = []; reviewElements.forEach((element) => { @@ -162,15 +136,6 @@ export const collectCoupangReviewData = return null; } - console.log("[voim] 수집된 데이터:", { - productId, - reviewRating: { - totalCount, - ratings: ratingsArray, - }, - reviews, - }); - // 데이터 유효성 검사 if (totalCount === 0) { console.error("[voim] 리뷰 수가 0입니다."); @@ -197,7 +162,7 @@ export const collectCoupangReviewData = }; } catch (error) { console.error("[voim] 리뷰 데이터 수집 중 오류:", error); - console.log("[voim] 현재 페이지 URL:", window.location.href); + return null; } }; @@ -206,8 +171,6 @@ export const sendReviewSummaryRequest = ( payload: ReviewSummaryRequestPayload, ): Promise => { return new Promise((resolve, reject) => { - console.log("[voim] 리뷰 요약 요청 시작"); - chrome.runtime.sendMessage( { type: "FETCH_REVIEW_SUMMARY", @@ -272,7 +235,6 @@ export const sendReviewSummaryRequest = ( isValidNegative && isValidKeywords ) { - console.log("[voim] 리뷰 요약 요청 성공"); resolve(res.data); } else { console.error("리뷰 요약 데이터 형식 오류", { diff --git a/src/content/coupang/cartHandler.ts b/src/content/coupang/cartHandler.ts index d015706..fce6808 100644 --- a/src/content/coupang/cartHandler.ts +++ b/src/content/coupang/cartHandler.ts @@ -57,7 +57,7 @@ export const extractCartItems = (): CartItem[] => { export const sendCartItemsToBackground = () => { const cartItems = extractCartItems(); - console.log("[CartHandler] 추출된 장바구니 아이템:", cartItems); + chrome.runtime.sendMessage({ type: "CART_ITEMS_UPDATED", data: cartItems, diff --git a/src/content/index.tsx b/src/content/index.tsx index b9a2631..2295cd3 100644 --- a/src/content/index.tsx +++ b/src/content/index.tsx @@ -56,7 +56,6 @@ export const observeAndStoreCategoryType = async () => { } const category = await detectCategoryType(); - console.log("[voim] 감지된 카테고리:", category); chrome.storage.local.set({ "voim-category-type": category }, () => { if (chrome.runtime.lastError) { @@ -64,8 +63,6 @@ export const observeAndStoreCategoryType = async () => { "[voim] 카테고리 저장 실패:", chrome.runtime.lastError.message, ); - } else { - console.log("[voim] 카테고리 저장 성공:", category); } }); const observer = new MutationObserver(() => { @@ -140,11 +137,6 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { const match = window.location.href.match(/products\/(\d+)/); const productId = match?.[1] ?? ""; - console.log("[voim][content] 감지 성공:", { - html: rawHtml.slice(0, 100), - productId, - }); - sendResponse({ html: rawHtml, productId }); }); diff --git a/src/content/styles/modeStyles.ts b/src/content/styles/modeStyles.ts index d44251a..abcb8ab 100644 --- a/src/content/styles/modeStyles.ts +++ b/src/content/styles/modeStyles.ts @@ -19,7 +19,7 @@ export function applyModeStyle( color: #e0e0e0 !important; } - a, p, span, li, td, th, h1, h2, h3, h4, h5, h6, strong, em { + a, p, span, li, td, th, h1, h2, h3, h4, h5, h6, strong, em, div { color: #e0e0e0 !important; background-color: transparent !important; border-color: #444 !important; diff --git a/src/components/fontButton/ControlFont.tsx b/src/tabs/fontButton/ControlFont.tsx similarity index 100% rename from src/components/fontButton/ControlFont.tsx rename to src/tabs/fontButton/ControlFont.tsx diff --git a/src/components/fontButton/component.tsx b/src/tabs/fontButton/component.tsx similarity index 98% rename from src/components/fontButton/component.tsx rename to src/tabs/fontButton/component.tsx index 66f5bc9..63a48ca 100644 --- a/src/components/fontButton/component.tsx +++ b/src/tabs/fontButton/component.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { CheckmarkIcon } from "../icons"; +import { CheckmarkIcon } from "@src/components/icons"; import { useTheme } from "@src/contexts/ThemeContext"; interface FontButtonProps { diff --git a/src/components/fontButton/index.ts b/src/tabs/fontButton/index.ts similarity index 100% rename from src/components/fontButton/index.ts rename to src/tabs/fontButton/index.ts diff --git a/src/components/modeButton/ControlMode.tsx b/src/tabs/modeButton/ControlMode.tsx similarity index 100% rename from src/components/modeButton/ControlMode.tsx rename to src/tabs/modeButton/ControlMode.tsx diff --git a/src/components/modeButton/component.tsx b/src/tabs/modeButton/component.tsx similarity index 96% rename from src/components/modeButton/component.tsx rename to src/tabs/modeButton/component.tsx index ca9761d..a91a861 100644 --- a/src/components/modeButton/component.tsx +++ b/src/tabs/modeButton/component.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { CheckmarkIcon } from "../icons"; +import { CheckmarkIcon } from "@src/components/icons"; import { useTheme } from "@src/contexts/ThemeContext"; interface ModeButtonProps { diff --git a/src/components/modeButton/index.ts b/src/tabs/modeButton/index.ts similarity index 100% rename from src/components/modeButton/index.ts rename to src/tabs/modeButton/index.ts diff --git a/src/tabs/myInfo/components/AllergySelectForm.tsx b/src/tabs/myInfo/components/AllergySelectForm.tsx index 73ea728..5b8e14f 100644 --- a/src/tabs/myInfo/components/AllergySelectForm.tsx +++ b/src/tabs/myInfo/components/AllergySelectForm.tsx @@ -62,6 +62,8 @@ type AllergyCategory = keyof typeof allergyData; const ALL_CATEGORY = Object.keys(allergyData) as AllergyCategory[]; export function AllergySelectForm({ onComplete }: { onComplete?: () => void }) { + const { theme } = useTheme(); + const isDarkMode = theme === "dark"; const [selectedCategory, setSelectedCategory] = useState( ALL_CATEGORY[0], ); @@ -131,7 +133,9 @@ export function AllergySelectForm({ onComplete }: { onComplete?: () => void }) {
선택한 알러지 확인하기
-
+
{selectedAllergies.map((item) => ( (null); // 👈 제목에 포커스하기 위한 ref + const sections = [ onboardingData.info, onboardingData.image, @@ -45,6 +47,10 @@ export function Onboarding({ onComplete }: OnboardingProps) { return () => window.removeEventListener("keydown", handleKeyDown); }, []); + useEffect(() => { + titleRef.current?.focus(); + }, [currentPage]); + return (
-
{sections[currentPage].title}
+
+ {sections[currentPage].title} +
@@ -65,10 +77,9 @@ export function Onboarding({ onComplete }: OnboardingProps) { {text.split("\n").map((line, i) => ( {line} -
+
))} -
))}
@@ -80,5 +91,3 @@ export function Onboarding({ onComplete }: OnboardingProps) {
); } - -export default Onboarding; diff --git a/src/components/serviceButton/ControlService.tsx b/src/tabs/serviceButton/ControlService.tsx similarity index 100% rename from src/components/serviceButton/ControlService.tsx rename to src/tabs/serviceButton/ControlService.tsx diff --git a/src/components/serviceButton/component.tsx b/src/tabs/serviceButton/component.tsx similarity index 96% rename from src/components/serviceButton/component.tsx rename to src/tabs/serviceButton/component.tsx index 78e49b3..87c5c8d 100644 --- a/src/components/serviceButton/component.tsx +++ b/src/tabs/serviceButton/component.tsx @@ -1,6 +1,6 @@ import { useTheme } from "@src/contexts/ThemeContext"; import React from "react"; -import { CheckmarkIcon } from "../icons"; +import { CheckmarkIcon } from "@src/components/icons"; interface ServiceButtonProps { children: React.ReactNode; diff --git a/src/components/serviceButton/index.ts b/src/tabs/serviceButton/index.ts similarity index 100% rename from src/components/serviceButton/index.ts rename to src/tabs/serviceButton/index.ts diff --git a/src/components/shortcutTab/component.tsx b/src/tabs/shortcutTab/component.tsx similarity index 97% rename from src/components/shortcutTab/component.tsx rename to src/tabs/shortcutTab/component.tsx index 5de432e..5e5936d 100644 --- a/src/components/shortcutTab/component.tsx +++ b/src/tabs/shortcutTab/component.tsx @@ -1,6 +1,6 @@ import { useTheme } from "@src/contexts/ThemeContext"; import React from "react"; -import { ContentBox } from "../contentBox"; +import { ContentBox } from "@src/components/contentBox"; export function ShortcutTab() { const { theme, fontClasses } = useTheme(); @@ -26,6 +26,14 @@ export function ShortcutTab() { aria-label="단축키 목록" tabIndex={0} > + +
+ 쿠팡 제품정보 열기 또는 접기 +
+
+ ALT + I +
+
메뉴 열기 또는 닫기 @@ -50,14 +58,6 @@ export function ShortcutTab() { ALT + A
- -
- 쿠팡 제품정보 열기 또는 접기 -
-
- ALT + I -
-
diff --git a/src/components/shortcutTab/index.ts b/src/tabs/shortcutTab/index.ts similarity index 100% rename from src/components/shortcutTab/index.ts rename to src/tabs/shortcutTab/index.ts