ns
ns-pos · Engineering Dashboard
Architecture · Impact · Roadmap
DIAGNOSED · DECISION PENDING v1.0 · 19 May 2026
01 · Tổng quan dự án

Codebase đã được chẩn đoán. Quyết định đang chờ.

Báo cáo kiến trúc và impact analysis trên 2.470 file JS/TS với 9.445 cạnh phụ thuộc đã hoàn tất. Hai hướng đi đang trên bàn — sửa stack hiện tại trong 9-12 tháng, hoặc viết lại native Android trong 10-12 tháng cho SUNMI D3 Pro.

Repo · ns-pos
NailSoft / Harmony
Target · SUNMI D3 Pro
Files in graph 2,470 JS/TS · 9.445 dependency edges
Largest cluster 472files Checkout/Basket · blast radius depth-3
Systemic hub 610files Store/state root · avoid early refactor
Quick win 26files Signal runtime · safe early cleanup
Calendar là lý do để rewrite, nhưng Checkout/Basket mới là cluster business coupling lớn nhất. Lộ trình phải chứng minh Calendar performance sớm, rồi nhanh chóng validate Calendar → Checkout → Payment → Receipt/Printer là đường POS thật sự. — Impact Analysis Findings, ns-pos
§ Quyết định cần làm tuần này
Decision A · Strategy High impact

Keep-stack hay Native rewrite?

Quyết định này ảnh hưởng 9-18 tháng tới. Keep-stack giữ được iOS parity và Salon/Retailer cùng một codebase. Native rewrite cho D3 hiệu năng cao hơn nhưng phải maintain hai app trong thời gian dài.

Keep-stack: 9-12 tháng Native: 10-12 tháng MVP
Decision B · Sequencing Medium impact

Bắt đầu từ đâu?

Cả hai phương án đều khuyên: không động vào store root (610 files) hay shared/utils (813 dependents) trước. Bắt đầu từ Signal runtime (26 files) hoặc Foundation + parity tests, rồi mới đến Calendar.

Signal first Foundation gates Avoid store root
§ Tình trạng hệ thống
Health by domain depth-3 blast radius
Calendar primary perf target
344
~71klines
Checkout/Basket largest business cluster
472
~110klines
Payment rides on checkout + receipt
343
~71klines
Printer/Receipt hardware risk
248
~75klines
Signal runtime early-cleanup candidate
26
~6klines
Top risks tracked
  • SUNMI D3 hardware chưa được tối ưu — JS work, render churn, Android scroll/gesture là bottleneck thật, không phải iOS smooth-scroll.
  • Realtime + heavy invalidation — SignalR events gọi thẳng vào layout recompute trong giờ cao điểm.
  • Hub filesshared/themes (817 dependents), utils/index (813), resources (499) khuếch đại blast radius cực nhanh.
  • JSON.stringify memoization ở hot paths là dấu hiệu unstable data shape.
  • Backend bottleneck (rewrite path) — chỉ 1 BE engineer, BFF/API contract chậm là FE chờ mock dài.
Recommended next action

Tuần 1-3: Build dependency baseline + parity test framework + D3 benchmark harness. Đây là việc giá trị bất kể chọn phương án nào — keep-stack hay rewrite đều cần parity gates và performance evidence để gate từng phase.

02 · Module debt map

Mỗi module = một cluster. Đo đo đo trước khi đụng.

Đo bằng yarn graph:impact ở depth-3 trên các seed file đại diện. Diện tích thật của một thay đổi là tổng số file/dòng nó kéo theo, không phải số file Claude/dev sửa trực tiếp.

Tool · graph:impact
Depth · 3
Run · 19 May 2026
Blast radius theo module (depth-3) sắp xếp giảm dần · 610 file = 100%
Store/state root redux/store + slices + reducers + saga
610
~143klines
Modal/app shell ModalProvider + MainStack + RootNavigator
494
~120klines
Checkout/Basket basketLocal + checkoutSelection + refresh
472
~110klines
Calendar calendar slice + CalendarBody + CalendarPage
344
~71klines
Payment payment + usePayBasket + usePaymentZCP
343
~71klines
Printer/Receipt usePrinter + usePrinterManager + PopupReceipt
248
~75klines
Customer customer search + create + detail
206
~64klines
Data layer request + useHarmonyQuery + RTKQuery
35
~9.7klines
Inventory inventory + stock + product
30
~6.4klines
Signal runtime SignalProvider + useSignalR
26
~6klines
Retailer retailer-specific screens
15
~4.5klines
Hub files — DO NOT TOUCH FIRST avoid

Số dependents trực tiếp. Một thay đổi ở đây = invalidate gần như cả app.

FileDependents
src/shared/themes/index.js817
src/utils/index.js813
src/resources/index.js499
src/shared/providers/ModalProvider.js183
src/redux/slices/index.js164
src/apis/index.js155
Overlap giữa các domain share file count

Khi cluster A và cluster B chia sẻ nhiều file, không thể refactor riêng một bên.

Cặp domainFiles chung
Calendar ↔ Checkout340
Calendar ↔ Payment340
Modal/Shell ↔ Checkout331
Checkout ↔ Printer236
Checkout ↔ Customer200
Printer ↔ Customer191
Đọc bảng overlap thế nào?

Tách công việc theo màn hình sẽ ước lượng thiếu. Tách theo vertical operating flow mới an toàn. Đường quan trọng nhất không phải "màn Calendar" mà là Calendar → Appointment Detail → Checkout → Payment → Receipt/Printer.

§ Module phân loại theo độ ưu tiên refactor
Bắt đầu được ngay Safe
  • Signal runtime (26 files) — small blast radius, unblocks Calendar batching
  • Data layer adapters (35 files) — RTK Query policy + adapter helpers
  • Performance harness — không phải file, là tooling foundation
Refactor có chiến lược Plan
  • Calendar (344) — phase 2, sau Signal runtime
  • Payment (343) — phase 4, sau Checkout
  • Printer/Receipt (248) — phase 5, isolation
  • Customer (206) — overlap với Checkout
Đụng cuối cùng Last
  • Store/state root (610) — sau khi domain boundaries ổn định
  • Modal/Shell (494) — global interaction layer
  • Checkout/Basket (472) — phase 3, sau Calendar
  • Hub files — chỉ thay vì incremental adapter pattern
03 · Calendar performance audit

Tám phát hiện. Mỗi cái có cost cụ thể.

Audit Calendar — màn hình hot nhất của Salon POS — bằng cách đọc code, git history và stack profile. Không phải tất cả đều phải sửa cùng lúc; cần ưu tiên theo cost-on-D3.

Source · Section 6
File · calendar slice + helpers
Target · SUNMI D3 Pro
Full layout recompute quá thường xuyên
F-6.1 · High
updateStaffLayoutcalculatorColumnStaffLayout bị trigger bởi 9 action: setCalendarDataByDate, removeStaff, updateBlocksBookingOnline, cancelAppointment, moveAppointmentToWaiting, updateAppointmentFromSignal, addAppointment, updateBlockTimes, updateTurnForStaffs. Mỗi sự kiện local kéo theo layout work của cả màn.
Selectors rebuild quá nhiều
F-6.2 · High
getStaffsAndBlockTimesDisplay map staff × filter blockTimes mỗi lần. getStaffDisplayAndAppointmentCount deep-clone bằng JSON.parse(JSON.stringify(staffs)). Vừa tốn CPU vừa tạo object reference mới → render churn cấp số nhân.
Layout algorithm không tầm thường
F-6.3 · Medium-High
Tính staff column widths, any-staff expansion, visible staff count, block layout per staff. Phần overlap riêng tính max simultaneous blocks, block relations, w/l/t/h. Tất cả nằm gần Redux hot update paths — phải tách thành Calendar layout/projection engine riêng.
Scroll architecture phức tạp
F-6.4 · Medium-High
Vertical ScrollView → horizontal body FlashList + horizontal header FlashList, sync qua useScrollSync. Hook quản lý leader/follower, animated refs, shared values, snapping, momentum end, programmatic scroll, iOS smooth-scroll edge cases. Đúng vấn đề, nhưng giải bằng cách add control logic.
iOS scroll stability nhận quá nhiều attention
F-6.5 · Mixed
Commits 591c89b39, 3751afdb7, 26f5a79e8, e39352391 + spec 002-ios-calendar-scroll-stability đầu tư mạnh vào iOS. Hữu ích — nhưng không phải bottleneck của D3. D3 cần JS work, render churn, Android scroll/gesture.
JSON.stringify memoization là warning sign
F-6.6 · Medium-High
CalendarHeaderStaffItem so sánh prev/next item bằng JSON.stringify. PanGestureContainer stringify reset params. useCalendarVerticalScroll, useHandleSignalCalendar stringify workingTimes trong deps. Giảm identity churn — nhưng che dấu data shape không ổn định.
Realtime updates quá gần heavy UI invalidation
F-6.7 · High
useHandleSignalCalendar.js xử lý 10+ event types: appointment_add/update/checkout, change_item, staff_position, update_waiting, turn_update, staff_update_block_booking_online, edit_blocktime_updatecalendar, caller events. Mỗi event dispatch vào calendar slice action có thể recompute layout — giờ cao điểm có thể tự thấy lag dù user không thao tác.
Drawing và GPU work tạo pressure
F-6.8 · Medium
BackgroundLayer dùng Skia Canvas/Line/Rect. DefaultListProps.CALENDAR + CalendarBody bật shouldRasterizeIOS, renderToHardwareTextureAndroid. Có thể giúp specific cases — nhưng trên Android POS hardware có thể tăng texture memory pressure. Phải verify trên D3, không assume tốt.
§ Devs làm đúng vs làm sai
Đã làm đúng keep
  • Dùng FlashList thay FlatList cho horizontal lists lớn
  • Tạo useScrollSync để control header/body synchronization explicit
  • Split calendar fetch thành nhiều phase trong calendar.js saga
  • Dùng InteractionManager.runAfterInteractions để defer dispatch
  • Giảm callback identity churn ở hot scroll code
  • Spec-driven approach: specs/002-ios-calendar-scroll-stability/ có thinking rõ ràng
Cần sửa fix
  • Layout work bị trigger từ Redux actions thay vì batched ở UI layer
  • Selectors deep-clone và rebuild quá thường xuyên
  • SignalR events đi thẳng vào layout recompute — chưa coalesce
  • iOS smooth-scroll polish > Android stability fundamentals
  • JSON.stringify dùng làm memoization hack thay vì sửa data shape
  • Calendar layout engine chưa được tách thành module riêng
04 · Strategy comparison

Sửa stack đang có, hay viết lại Android native?

Cả hai con đường đều khả thi. Nhưng chúng tối ưu cho mục tiêu khác nhau. Quyết định phụ thuộc vào việc team coi iOS paritySalon/Retailer unified codebase quan trọng đến đâu so với D3 hiệu năng top tier.

Time horizon · 9-18 tháng
Team · 3 FE + 1 BE
Target · SUNMI D3 Pro
Phương án A
Keep-Stack Refactor

Giữ React Native, sửa kiến trúc tại chỗ. 6 phase, ưu tiên Calendar và Checkout boundaries.

Estimate
9–12 tháng
Team
2–4 engineers
Min viable
6 tháng / 2-3
Risk profile
Incremental

Pros

  • Salon + Retailer cùng codebase
  • iOS parity giữ nguyên
  • Không cần parallel maintain hai app
  • Code/test/release pipelines hiện có
  • Incremental — gate sau mỗi phase
  • Reuse Caller, Customer, Inventory logic

Cons

  • D3 vẫn chạy React Native bridge
  • Performance ceiling thấp hơn native
  • Store root (610) khó đụng triệt để
  • Phải maintain compatibility paths lâu
  • Refactor stretch dễ stall ở phase 3-5

When to pick

Khi iOS parity và Salon+Retailer unified codebase đáng giá hơn 30-50% performance gain trên D3. Khi team không muốn maintain hai app trong 1+ năm.

Phương án B
Native Android Rewrite

Kotlin + Compose + BFF. Android-first, D3-only cho v1. 7 phase, parity gate ngặt.

Estimate
10–12 tháng
Team
3 FE + 1 BE
Full parity
14–18 tháng
Risk profile
Bet-the-product

Pros

  • D3 native performance ceiling cao nhất
  • Compose UI + Skia trực tiếp
  • Coroutines + Flow + Room đơn giản hơn Redux/saga
  • BFF chuẩn hóa contract → backend rõ ràng
  • Bỏ được tech debt cumulative
  • MVI explicit hơn implicit Redux mess

Cons

  • Maintain hai app trong 10-12+ tháng
  • iOS không được benefit
  • Retailer mode out of MVP
  • 1 BE engineer = backend bottleneck
  • Hidden business rules ở RN dễ miss
  • Phải tự build lại Caller, Customer, modal flows

When to pick

Khi D3 là target chính, Retailer mode có thể defer, và team OK maintain hai app trong 1+ năm. Khi product muốn reset tech debt thay vì incremental cleanup.

§ Decision matrix theo tiêu chí
Tiêu chí
Phương án A · Keep-Stack
Phương án B · Native Rewrite
Thời gian đến production trên D3
9-12 tháng
10-12 tháng MVP
D3 performance ceiling
Khá
Cao nhất
iOS được benefit
Không
Salon + Retailer unified
Defer Retailer
Maintain hai codebase
Không
Có, ~12 tháng
Risk reset tech debt
Partial
Reset hoàn toàn
Risk hidden business rules
Thấp
Cao
Backend work
Vừa (RTK Query policy)
Lớn (BFF + contract)
Có thể stop giữa chừng
Sau mỗi phase
Sau Phase 4 mới có giá trị
Lưu ý

Foundation work của 2 phương án overlap đáng kể: parity matrix, performance harness, benchmark fixtures, realtime event contract. Không cần quyết định strategy ngay tuần 1 — có 3-4 tuần foundation work có thể bắt đầu trước.

05 · Roadmap · cả hai phương án

Timeline ở mức tuần. Phase gate, không deadline cứng.

Cả hai roadmap đều dùng phase gates thay vì calendar deadlines. Một phase không pass gate = không qua phase tiếp theo. Số tuần là range, dùng upper bound để vẽ.

Scale · tuần
Gating · phase exit criteria
Updated · 19 May 2026
Phương án A · Keep-Stack Refactor ~47-67 tuần · 6 phase
T0 T10 T20 T30 T40 T50 T60
Phase 1 · 3-5 tuần Foundation & Measurement
FOUND
Phase 2 · 10-14 tuần Signal + Calendar
SIGNAL + CALENDAR
Phase 3 · 10-14 tuần Checkout & Basket
CHECKOUT
Phase 4 · 6-8 tuần Payment
PAYMENT
Phase 5 · 10-14 tuần Printer · Receipt · Caller
PRINTER + RECEIPT
Phase 6 · 8-12 tuần Shared + Data layer
SHARED + DATA
Phương án B · Native Android Rewrite ~57-76 tuần · 7 phase · 3 FE + 1 BE
T0 T10 T20 T30 T40 T50 T60 T70
Phase 0 · 3-4 tuần Parity & Contract Freeze
P0
Phase 1 · 6-8 tuần Native Foundation + BFF
FOUND + BFF
Phase 2 · 12-16 tuần Calendar Vertical Slice
CALENDAR SLICE
Phase 3 · 10-14 tuần Checkout / Basket
CHECKOUT
Phase 4 · 12-16 tuần Payment · Receipt · Printer
PAYMENT + RCPT + PRINT
Phase 5 · 8-10 tuần Caller · Customer · Settings
CALLER + CUST
Phase 6 · 6-8 tuần Pilot & Cutover
PILOT
§ Phase gates · cả hai phương án
Keep-Stack · gate per phase A
  • P1. Performance baseline + parity gates + feature flags + test:calendar, test:checkout
  • P2. Calendar state/projection normalized + event coalescing + Android scroll authority đơn giản hóa
  • P3. Basket model normalized + selection state tách + refresh/invalidate rules isolated
  • P4. Payment result contract định nghĩa + rules tách khỏi checkout widgets
  • P5. Receipt VM + printable payload + driver adapters tách rời
  • P6. RTK Query migration, shared/hooks cleanup, Customer/Inventory boundaries
Native Rewrite · gate per phase B
  • P0. Parity matrix tất cả flow + benchmark fixtures + BFF/API contract drafts
  • P1. App login + API + realtime + benchmark harness chạy trên D3
  • P2. Calendar visibly + measurably faster than old app · drag/drop MVP · handoff to Appointment Detail
  • P3. Calendar → Checkout → Calendar roundtrip không lose/corrupt state
  • P4. Payment complete → receipt → print pass on real D3 hardware
  • P5. Một store hoạt động được một ngày Salon POS trên D3 staging
  • P6. Không còn P0/P1 parity gap nào với Calendar, Checkout, Payment, Printer
06 · Capacity & cost

Bao nhiêu người, bao nhiêu tháng, đổi lấy gì?

Estimate dựa trên impact analysis thật + parity gates. Phân công team theo vertical operating flow, không theo screen — vì overlap giữa các domain quá lớn.

Reference team · 3 FE + 1 BE
Senior level
Full-time on project
§ Phân công team (phương án Native Rewrite)
F1
FE1 · Calendar & Realtime UI
Vertical owner · Calendar slice + perf gates
  • Calendar surface (Compose hoặc custom View/Canvas)
  • Projection/layout engine
  • Drag/drop + gesture system
  • Signal event application to UI
  • D3 performance benchmark hệ thống
F2
FE2 · Checkout & Payment
Vertical owner · Basket + payment rules
  • Basket domain (MVI state + invalidate rules)
  • Service/staff/category selection
  • Payment rules (cash/card/split/tip/redeem)
  • Checkout/payment handoff orchestration
F3
FE3 · Platform, Printer, Shell
Horizontal · everything that's not vertical
  • App shell + navigation + session
  • Room/cache infrastructure
  • Printer/receipt (SUNMI/D3 + Epson/Star adapters)
  • Dialog/workflow system + design system
B1
BE1 · BFF + API + Realtime contracts
Backend bottleneck · contract owner
  • Aggregate APIs (Calendar, Checkout, Customer)
  • Normalized backend response shape
  • Payment/receipt contracts
  • Realtime event contracts
  • Compatibility với backend hiện tại
Risk

1 BE engineer là single point of failure. BFF/API contract chậm = FE chờ mock dài hơn = parity risk tăng.

§ Estimate theo scope
Scope Phương án Team Thời gian Ghi chú
Performance rescue + architecture foundation A 2-3 senior ~3 tháng Narrower scope, không full gap closure
Foundation + Calendar + Signal + Checkout + part Payment/Printer A 2-3 senior ~6 tháng Realistic minimum viable
Toàn bộ gaps (Customer, Inventory, Settings, Retailer cleanup) A 2-4 engineers 9-12 tháng Stronger sense of "fix everything"
Android D3 Salon core MVP B 3 FE + 1 BE 8-10 tháng Calendar, Checkout, Payment, Receipt, Caller, Customer basic
Production-ready Android replacement (Salon core) B 3 FE + 1 BE 10-12 tháng Pilot + cutover + parity report
Broad Android parity với most old-app behavior B 3 FE + 1 BE 14-18 tháng Bao gồm Retailer, Reports, deep Settings
Full parity including Retailer + Reports + deep Settings B 3 FE + 1 BE 18+ tháng Marketing flows, advanced inventory
§ Workstream breakdown (Keep-Stack)
Foundation3-5w

Governance + tests + baseline + performance harness

Calendar refactor8-12w

State normalization, projection engine, scroll authority

Signal runtime2-4w

Event queue + batching, đi cùng Calendar

Checkout/Basket10-14w

Selection ≠ basket state, refresh rules isolated

Payment rules6-8w

Result contract, rules tách khỏi widgets

Printer/Receipt/Caller10-14w

VM/payload/driver split + caller lifecycle

Shared + data layer8-12w

RTK Query migration, domain hooks move-out

Total (cumulative)~47-67 weeks

9-12 tháng với 2-4 engineer, có cushion cho discovery

07 · Toolkit

Impact analysis CLI + Calendar refactor slice template

Hai thứ làm việc với bất kể phương án nào: bộ command graph:* để đo blast radius trước khi đụng, và template refactor slice cho Calendar (đã pre-filled từ impact analysis).

Tools · yarn graph:*
Template · refactor-calendar-slice
§ CLI quick reference
Build dependency graph ~30s
# Quét toàn bộ src/, tạo graph snapshot
yarn graph:build

Chạy 1 lần khi bắt đầu, hoặc khi merge branch lớn. Lưu vào .graph-cache/.

Đo impact của file/seed ~5s
# Default depth-3
yarn graph:impact \
  src/redux/slices/salon/calendar/index.js

# Nhiều seed cùng lúc
yarn graph:impact \
  src/redux/slices/salon/calendar/*.js
Visualize subgraph browser
# Mở SVG viz trong browser
yarn graph:viz \
  src/shared/providers/ModalProvider.js

Highlight hub files (đỏ) và leaf files (xanh).

§ Workflow theo loại task
Loại task Workflow Blast radius limit
Bug fix graph:impact file gốc → nếu >50, cân nhắc patch local thay vì shared edit <50
Small change graph:impact trước khi viết code, đọc top 10 file affected <100
New feature graph:impact các seed dự kiến touch, viết ADR ngắn nếu >200 <200
Refactor Slice plan kiểu Hop 1/2/3 dùng graph:impact per hop tracked
Upgrade lib graph:impact import path → biết phạm vi changelog cần đọc tracked
Performance graph:viz để hiểu render tree, không chỉ profile
Large refactor Plan đầy đủ với gate per hop. Re-run graph:impact sau mỗi hop để verify blast không tăng staged
§ Calendar refactor slice — pre-filled template

Slice cụ thể để refactor Calendar theo Keep-Stack Phase 2. Đã chạy graph:impact để pre-fill blast radius cho mỗi hop. Mỗi hop có gate riêng — không qua gate là không qua hop tiếp.

Hop 1 · Extract layout engine ~80 files
  • shared/utils/calendar tách thành calendar/projection/
  • Move Helpers.js calc functions ra projection module
  • Inject vào slice qua adapter, không thay slice contract
  • Unit test projection independently
Gate

Projection engine có 80%+ test coverage. Calendar slice không có behavior change.

Hop 2 · Coalesce signal events ~110 files
  • Thêm event queue giữa useHandleSignalCalendar và slice
  • Coalesce burst (5+ events trong 100ms) thành 1 batch
  • Defer layout invalidation qua InteractionManager
  • Benchmark trên D3 với realtime burst fixture
Gate

Realtime burst (20 events/s) không drop FPS dưới 45 trên D3.

Hop 3 · Normalize calendar state ~240 files
  • Tách selection state ra calendarSelection slice
  • Replace JSON.parse(JSON.stringify(...)) bằng immer/structuredClone
  • Stable selector references (reselect + memoize-one)
  • Replace JSON.stringify memoization bằng deep-equal stable hooks
Gate

Selectors không rebuild khi unrelated slice action dispatch. Render churn giảm >50% trên D3.

§ Critical path · cả hai phương án
Foundation
   ↓
Calendar
   ↓
Calendar/Checkout handoff
   ↓
Payment
   ↓
Receipt/Printer
   ↓
Pilot
Đọc critical path

Đường ngắn nhất từ "có codebase" đến "có thể bán cho merchant". Mỗi node phụ thuộc node trước. Tăng tốc Calendar không giúp gì nếu Payment chưa pass D3 hardware test. Refactor Inventory không nên đặt trên critical path.