Skip to content

feat(serial, msix): Serial 포트 선택 RPC 추가 및 MSIX 코드 서명·배포 구성#334

Closed
hakueon wants to merge 108 commits into
scratchfoundation:developfrom
aluxrobot:feature/serial-port-selection
Closed

feat(serial, msix): Serial 포트 선택 RPC 추가 및 MSIX 코드 서명·배포 구성#334
hakueon wants to merge 108 commits into
scratchfoundation:developfrom
aluxrobot:feature/serial-port-selection

Conversation

@hakueon

@hakueon hakueon commented Jun 11, 2026

Copy link
Copy Markdown

feat(serial, msix): Serial 포트 선택 RPC 추가 및 MSIX 코드 서명·배포 구성

주요 변경 사항

  • listSerialPorts RPC — 매칭되는 COM 포트 전체를 한 응답으로 반환(스트리밍·부수효과 없음). 자동 첫-포트 연결만 가능하던 한계를 해소.
  • Serial peripheralId를 COM명으로 통일GeneratePeripheralId 훅 추가, 시리얼만 COM path 사용(BLE/BT는 GUID 유지). discover/list 열거를 DoEnumeratePorts로 일원화.
  • MSIX Publisher 신원 설정 — Sectigo OV 인증서 Subject와 매니페스트 Publisher 일치, 노출용 표시명은 "ALUX Inc."로 분리.
  • 배포 아키텍처 축소 — AppxBundlePlatforms에서 arm64 제외 (x86/x64만).
  • S3 배포 자동화make sync-s3를 파일별 aws s3 cp(올바른 MIME) + CloudFront 무효화로 교체, 무효화 경로 변환 버그(MSYS_NO_PATHCONV) 수정.
  • .appinstaller 정본/버전 관리 — prod/dev .appinstaller 추가 및 실제 배포 버전(1.0.1.1031)으로 갱신.
  • 문서 — 코드 서명·MSIX 배포·자동 업데이트 가이드 추가, 호스팅을 scratch-link로 확정.

hakueon and others added 30 commits May 22, 2026 12:36
Introduce design documents for a new USB Serial transport extension
on scratch-link 2.x. SerialTransport.md describes the server side:
a /scratch/serial JSON-RPC endpoint on ws://localhost:20111, targeting
CH340 (VID 0x1A86 / PID 0x7523) on Windows first via
System.IO.Ports.SerialPort and WMI-based VID/PID enumeration.
scratch-link-fork-plan.md is the aluxcoding-scratch client-side
counterpart aligned to the same wire contract.

Uses Serial-specific RPC vocabulary (serialDidReceiveData,
serialDidDisconnect, startReading/stopReading) so callers cannot
confuse Serial events with BLE or BT events.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the BLE/BT session pattern with a /scratch/serial JSON-RPC
endpoint. CH340 (VID 0x1A86 / PID 0x7523) is first-class via WMI-based
VID/PID enumeration; other CDC devices work through the same path with
appropriate discovery filters.

Why a separate listener port (20211): this fork is meant to coexist
with the stock scratch-link on user machines (e.g. existing Scratch 3.0
users), so the default 20110/20111 cannot be reused.

Why sync SerialPort.Read on a Task-Run thread instead of
BaseStream.ReadAsync: .NET 6's SerialStream does not override the
async read overloads in a way that survives CH340's open-time DTR/RTS
toggling; the first ReadAsync reliably throws ERROR_OPERATION_ABORTED
even with the cancellation token unbound. Synchronous Read with a
500ms ReadTimeout + TimeoutException-as-loop-tick matches the working
test_cs reference and lets shutdown be observed via the CTS.

DtrEnable/RtsEnable are pinned low explicitly because some firmware
(codetinker on CH340) treats DTR/RTS transitions as reset signals.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sets fork identity across assembly/package metadata, manifests, tray
UI, and icon assets so users can distinguish this binary from the
stock scratch-link they may have installed in parallel (see the port
20211 split in the Serial transport commit).

ScratchVersion.targets gains a floor of 1.0.0 when GitInfo finds no
semver tag, so an untagged build ships as "Alux Scratch Link 1.0.0.x"
instead of "0.0.x". Real releases are still cut by tagging.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SerialTransport.md and scratch-link-fork-plan.md moved to a sibling
documents/ folder outside this repo so the fork's design notes can be
edited alongside the related client-side work without churning this
tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UpgradeLog*.htm, _UpgradeReport_Files/, and Backup*/ are produced by
VS's project upgrade flow and are IDE-local — they should not enter
the repo when someone happens to open the solution after a toolchain
bump.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds brand/alux-l.svg as the source of truth for app/tray/MSIX icons,
plus brand/build_icons.py to regenerate them. Generated files are
already committed; the script only needs to run when the SVG changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rebrands the title and removes upstream-only content (macOS/Safari,
semantic-release, CFBundle notes). Adds a fork notice with the AGPL-3.0
attribution to scratchfoundation/scratch-link, documents the Serial
transport and the coexistence port (20211), and points to
brand/build_icons.py for icon regeneration. Translated to Korean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Captures the VS 2026-specific workload names, the missing .NET 6 SDK
and Windows App Runtime 1.3 manual installs, and the startup-project
gotcha (scratch-link-win, not the wapproj) that causes MddBootstrap
to fail on F5.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces brand/alux-l.svg with brand/labs-l.svg as the icon source of
truth and regenerates all derived ICO/PNG assets. Wrong source file
was committed earlier.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feat(serial-transport): USB Serial 장치 지원 추가 및 포크 재브랜딩
- TargetFramework net6.0 → net8.0-windows10.0.22621.0
- RuntimeIdentifiers win10-* → win-* (.NET 8 RID 정책 변경)
- WindowsAppSDK 1.3.230331000 → 1.5.240311000
- SDK.BuildTools 10.0.22621.756 → 10.0.22621.3233
- System.IO.Ports 6.0.0 → 8.0.0, System.Management 6.0.2 → 8.0.0
- PublishProfiles win10-* → win-* 파일 이름 및 RID 일치
- wapproj AssetTargetFallback net6 → net8, publish profile 경로 수정

Win App Runtime 1.3 별도 설치 불필요 (1.5+ 이미 Windows에 탑재)
.NET 6 SDK EOL 별도 설치 불필요 (SDK 10.x에서 net8.0 직접 빌드)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
WinUI 3 requires both Windows 8 and Windows 10 supportedOS GUIDs.
Without the Win 10 GUID, the OS may run the app in Win 8 compat mode,
causing incorrect DPI/input/WinRT behavior on Windows 10 1809+ targets.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moved to external documents directory. No longer tracked in the repo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Debug_Win: WindowsAppSDKSelfContained=true so F5 works on a fresh dev
machine without pre-installing Windows App Runtime 1.5.

Release_Win (MSIX): publish profiles override to false so the MSIX
declares a runtime dependency and end-user installers stay lean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previous condition only matched 'Debug_Win' but VS default config is
'Debug', so fresh builds got SelfContained=false and showed the
Windows App Runtime install dialog on every first run.

Setting true unconditionally in csproj; all publish profiles already
override to false for MSIX packaging so release builds are unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Windows-only fork does not need Debug_Mac, Release_Mac,
Release_DevID_Mac, Release_MAS_Mac configurations or the
scratch-link-mac project entry. Drops the VS config dropdown
from 6 configs to 2 (Debug_Win, Release_Win).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
VS 2026 MSBuild resolves $(NuGetPackageRoot) differently from the CLI,
causing the Exists() condition on the GitInfo.targets import to fail
silently. This leaves GitVersion undefined and breaks the build.

Add a fallback GitVersion target in ScratchVersion.targets that sets
safe defaults (SemVer 0.0.0, triggering the 1.0.0 floor). GitInfo's
own GitVersion is imported later via nuget.g.targets and overrides
this fallback when the package loads correctly ("last definition wins").

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AnyCPU platform does not support self-contained mode and throws
'The platform AnyCPU is not supported for Self Contained mode'.

Self-contained (bundles App Runtime DLLs) is now enabled only when
building with x64, x86, or ARM64. AnyCPU falls back to false, which
is fine since the wapproj always builds with a specific platform anyway.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bumps Microsoft.WindowsAppSDK from 1.5.240311000 to 1.8.260508005 and
Microsoft.Windows.SDK.BuildTools from 10.0.22621.3233 to 10.0.28000.1839.
Windows App Runtime 1.8 is more widely pre-installed on Windows 11 via
Windows Update, eliminating the runtime-install dialog on most systems.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Updates README to reflect actual project structure:
- .NET 8 / WindowsAppSDK 1.8 (was .NET 6 / 1.2/1.3)
- Repo directory tree with all components
- Accurate system requirements (WAR 1.8, Win 10 1809+)
- Dev setup quick-start pointing to WindowsDevSetup-VS2026.md
- Build configuration table (Debug_Win / Release_Win)
- Corrected packaging section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename package.json name/author/version to ALUX, Inc. / 1.0.0
- Update csproj and appxmanifest Company/PublisherDisplayName to ALUX, Inc.
- Update README: AluxLabs branding, Alux product extensions, repo link to dev guide
- Move dev setup guide into Documentation/ for proper git tracking

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Upgrade .NET 6 → .NET 8, WindowsAppSDK 1.3 → 1.8
- Fix RID naming for .NET 8 (win10-* → win-*)
- Add fallback GitVersion target for VS 2026 MSBuild compatibility
- Add Windows 10 compatibility GUID to app.manifest
- Update project metadata to ALUX, Inc.
- Move dev setup guide into Documentation/
- Rewrite README for current project state
Group all fork-specific documentation under a dedicated subfolder so
that upstream scratchfoundation/scratch-link docs and Alux fork docs
are visually and structurally distinguishable. This also lets upstream
syncs ignore Documentation/Alux/ as a whole.

Contents:
- SerialApiReference.md: complete JSON-RPC API for the serial transport
  (discover, connect, write, startReading, stopReading, setKeepAlive,
  disconnect; notifications; recovery policy; pathHint matching
  semantics).
- SerialKeepAliveGuide.md: explains the codetinker 1s-timeout problem,
  the keep-alive timer design, the DFU-safe interaction model
  (write-time reset, setKeepAlive toggle), and the wireTrace
  diagnostic option.
- WindowsDevSetup-VS2026.md: moved into Alux/ alongside the new guides.

README links the new folder so upstream-vs-fork doc origin is obvious.
… setKeepAlive, and wireTrace diagnostic

Adds three connect-time options and one runtime RPC to the serial
transport so that wireless devices like Codetinker — which require
"<" 1s between TX packets or they signal a timeout alarm — can be
driven safely while still allowing transports that have to send
arbitrary byte streams (e.g. firmware update) to suppress the
keep-alive on demand.

Connect parameters:
- peripheralType (string, optional): identifies the device class
  ("codetinker", "connect", "technic", …). Logged at connect time;
  reserved for future per-device policy.
- keepAliveIntervalMs (int, optional): if set, the most recently sent
  TX packet is re-sent at this interval to keep the device from
  timing out during idle periods. Null or non-positive disables.
- wireTrace (bool, optional): emits per-write/per-read hex dumps via
  Trace.WriteLine for transport-level debugging. Off by default.

Runtime RPC:
- setMode-style toggle via setKeepAlive { intervalMs }: lets the
  client disable keep-alive before a firmware update (intervalMs=null)
  and re-enable it after (intervalMs=33). The cached last-TX packet
  is preserved across the toggle, the call is idempotent, and
  stopping blocks on any in-flight tick so no resend ever races
  with a disconnect.

Thread-safety:
- SemaphoreSlim serializes HandleWrite vs. keep-alive ticks so two
  DoWrite invocations never overlap and corrupt the stream.
- The keep-alive timer fires an async-void callback with skip-on-busy
  semantics (WaitAsync(0)), making it idle-only — write bursts (DFU
  chunks) automatically suppress resends until the line goes idle.
- HandleWrite resets the timer on every write so the resend never
  fires mid-burst even at the boundaries.
- StopKeepAlive uses Timer.Dispose(WaitHandle) to block until the
  current tick finishes; ResetKeepAliveTimer swallows
  ObjectDisposedException for the race window with stop.
- Dispose(bool) is overridden to tear down the timer and dispose the
  semaphore in the correct order; platform subclasses still close
  their port handle.

Structured Trace logs for start, stop, double-start, and resend
failure surface keep-alive behaviour in production logs.
… to eliminate driver race

.NET 8 SerialPort + CH340/CP210x drivers throw spurious TimeoutException
on the read side whenever a Write fires while a blocking Read is in
flight, regardless of whether the Write uses BaseStream.WriteAsync or
the synchronous SerialPort.Write. Symptom in production: with
keep-alive on at 33ms, the burst comes at the write cadence and the
client disconnects after ~1.4s, breaking wireless DFU. Diagnosed by
disabling keep-alive — the burst disappears and only normal 500ms idle
timeouts remain (caught and ignored).

Fix: guarantee Read and Write are never in flight at the same time on
the same handle.

ReadLoop:
- Poll SerialPort.BytesToRead (a cheap status query that does not hold
  the I/O surface) and only call Read when bytes are actually
  available, so Read returns immediately rather than blocking on a
  timeout.
- Read is wrapped in ioLock so it never overlaps DoWrite.
- When idle, wait on the cancellation token's WaitHandle for ~10ms
  instead of issuing a blocking Read; this also removes the cosmetic
  500ms idle TimeoutException spam previously seen in the debugger.

DoWrite:
- Switch from BaseStream.WriteAsync to the synchronous SerialPort.Write
  so Read and Write travel through the same SerialPort cache layer
  rather than mixing BaseStream and cache-layer APIs.
- Wrap the synchronous Write in ioLock and dispatch on the ThreadPool
  via Task.Run to preserve the async signature.
- Re-check IsOpen inside the lock; catch InvalidOperationException
  (which the synchronous Write can throw on a closed port) and map it
  to the existing invalid-request error path.

Because ReadLoop only acquires ioLock when data is present, the lock
hold time is dominated by the immediate-return Read; keep-alive
writes at 33ms see sub-millisecond contention.

Verified in production with both wired and wireless (USB dongle) DFU:
the TimeoutException burst is gone, bootloader entry succeeds, and
post-firmware reinit reconnects cleanly.
upstream 자동 생성 변경 이력 파일을 제거하고,
이 프로젝트에 맞는 Claude 행동 규칙 문서(CLAUDE.md)를 추가한다.
Personal debug captures (VS DebugView output, browser console logs,
etc.) live under ref/ during diagnosis but should never be committed.
Excluding the whole directory so accidental 'git add -A' won't pull
them in.
본 세션에서 추가/수정한 Serial 관련 코드의 주석을 CLAUDE.md §4 규칙에
맞춰 정리한다.

- 공용 코드(scratch-link-common)에서 특정 프로토콜/디바이스 언급 제거:
  SerialOpenParams.PeripheralType 문서, StartKeepAlive 문서에서 "codetinker"
  같은 device-specific 예시를 빼고 일반화된 표현으로 바꾼다.
- 일반 주석(//)을 한 줄로 축약: 멀티라인 설계 의도 설명은 commit log /
  문서로 옮기고, 인라인 주석은 WHY 한 줄만 남긴다 (writeSemaphore/stateLock/
  wireTrace 필드 주석, HandleSetKeepAlive idempotent 주석, StopKeepAlive
  block 주석, Dispose 주석, OnKeepAliveTick idle-only/re-check/escape 주석,
  WinSerialSession ioLock/DoWrite/ReadLoop polling 주석 등).
- XML doc 단순화: 자명한 WHAT 설명과 caller 참조("Called from X")를
  제거하고, 비자명한 동작·제약·사이드이펙트만 단일 <summary> 로 남긴다.
  StyleCop SA1611/SA1615 충족을 위해 <param>/<returns>는 짧게 유지.

동작/API 변경 없음. 빌드 클린(우리 코드 관련 새 warning 없음).
feat(serial): Serial 전송에 keep-alive 및 진단 기능 추가, driver race 수정
hakueon and others added 27 commits June 5, 2026 08:32
fix(msix, win): MSIX 사이드로딩 패키징 정상화 및 트레이 아이콘 수정
…로 통일

picker UI에서 사용자가 연결할 COM 포트를 직접 고를 수 있도록, 매칭 포트 전체를
한 응답으로 돌려주는 listSerialPorts RPC를 추가한다. 자동 첫-포트 연결만 가능하던
기존 link 모드의 한계를 닫는다.

- listSerialPorts: 현재 시점 매칭 포트 전체 스냅샷을 result.ports로 반환(스트리밍 없음).
  매칭 없으면 빈 배열, 부수효과 없음(포트 open 안 함), 활성 연결 중 호출 안전.
  반환 포트를 connect 레지스트리에 등록하고, 재호출 시 치환(stale id는 connect 시 -32600).
- discover/list가 공유하는 DoEnumeratePorts 스냅샷 추상화로 열거 일원화.
- PeripheralSession에 GeneratePeripheralId virtual 훅 추가. 시리얼만 COM path를
  peripheralId로 사용(SoT 정렬), BLE/BT는 익명 GUID 유지.
- SerialApiReference: listSerialPorts 섹션 추가 + connect 실패 에러 정정
  (-32603 -> -32500, 상세는 error.data).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sectigo OV(eToken) 서명 절차, signtool 사용법, Publisher 일치 규칙,
.appinstaller 기반 자동 업데이트, 호스팅 선택지(GitHub / AWS S3) 정리.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SignPath Foundation OSS 신청서 제출에 필요한 다운로드 페이지의 코드사이닝
주체 명시 요건 충족.
upstream의 ci.yml은 main 브랜치·semantic-release·fastlane·macOS 빌드를 전제로
하는데, AluxLabs Link fork는 main 브랜치를 두지 않고 별도 release.yml(PR #5)로
빌드/배포를 진행한다. push 시마다 \`fatal: couldn't find remote ref main\` 으로
실패해 PR 체크가 무조건 빨갛게 표시되는 노이즈를 제거.

workflow_dispatch만 남겨 수동 검증·디버깅 가능성은 유지. PR #5 머지 시 이 파일은
완전히 삭제될 예정.
Scratch Foundation의 CLA 서명 검사 봇이 우리 fork에서는 동작 의미가 없고,
scratchfoundation/scratch-agreements 접근 PAT가 설정돼 있지 않아 매번 실패함.
PR 체크가 무조건 빨갛게 표시되는 노이즈 제거.

workflow_dispatch만 남겨 필요 시 수동 실행 가능.
ci: 레거시·upstream 워크플로(ci.yml + signature-assistant.yml) 자동 트리거 비활성화
docs: README에 SignPath Foundation 코드사이닝 명시
…oudfront

# Conflicts:
#	.github/actions/macos-build/action.yml
#	.github/actions/windows-build/action.yml
#	.github/workflows/ci.yml
feat(cicd): S3 + CloudFront 배포 파이프라인
SignPath Foundation 심사 시 비영어권 README의 가독성을 보완하기 위한 안전 마진.
영문 요약에 프로젝트 성격, 라이선스(AGPL-3.0-only), upstream fork 관계,
SignPath Foundation 코드사이닝 사실을 한 박스에 집약.
docs: README 최상단에 영문 요약 박스 추가
2026-06-08 제출 시점의 모든 필드 입력값, Description·Reputation 본문,
선행 PR 목록(#5, #9, #10, #11), 승인 후 후속 작업 TODO, 자격 박탈 시
대체 옵션을 한 문서에 보존.

향후 자격 갱신·재신청·다른 사이닝 옵션 전환 시 참고용.
docs: SignPath Foundation 신청서 제출 내용 기록
Sectigo OV 인증서 Subject(CN="ALUX Co.,Ltd", O="ALUX Co.,Ltd",
S=Seoul, C=KR)와 매니페스트 Publisher를 일치시킴 (MSIX 서명 필수 조건).
사용자 노출용 PublisherDisplayName은 브랜드명 "ALUX Inc."로 분리.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- .appinstaller 정본 추가 (prod: scratch-assets/link, dev: dev-scratch-assets/link)
- 호스팅 확정: 토큰 수동 배포는 scratch-assets/link, CI(scratch-link)와 분리
- Publisher 확정값(CN="ALUX Co.,Ltd"...) 및 서명 검증 결과 문서화
- 릴리스마다 버전 3곳 갱신 규칙, MIME 타입 명시

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- AppxBundlePlatforms에서 arm64 제외 (대상 사용자 없음, 번들 용량↓).
  CI release.yml은 플랫폼을 명시 지정하므로 영향 없음.
- prod .appinstaller 버전을 실제 빌드(1.0.0.1028)에 맞춤.
- dist/upload/(서명된 대용량 번들 스테이징)를 .gitignore에 추가.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- .appinstaller 도메인 scratch-assets → scratch-link(공개 CDN 있는 곳).
  scratch-assets는 공개 DNS가 없어 제외, link 하위폴더 없이 루트 사용.
- 문서: 호스팅 절을 aws s3 cp + CloudFront 무효화 절차로 갱신
  (배포 ID, MIME 함정, /sha1 서명 권장, 인증서 지문 추가).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
aws s3 sync는 Content-Type을 지정하지 못해 .appinstaller/.msixbundle에
binary/octet-stream이 붙어 설치가 깨졌다. 파일별 aws s3 cp(--content-type)
+ cloudfront create-invalidation로 교체. 번들 파일명은 dist/upload/에서
와일드카드로 자동 인식, 스테이징 비었으면 에러로 조기 중단.
dev 타겟은 dev 도메인용 .appinstaller(dist/AluxLabsLink.dev.appinstaller)를 사용.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
git-bash/MSYS sh가 무효화 경로 인자(/AluxLabsLink.appinstaller)를
Windows 경로로 변환해 "invalid invalidation paths"로 실패했다.
export MSYS_NO_PATHCONV := 1 로 변환을 비활성화. (업로드 경로는 앞 / 가
없어 영향 없었고 무효화만 실패했음)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
prod/dev .appinstaller를 실제 배포한 1.0.1.1031에 맞춤.
자동 업데이트 동작 검증을 위한 버전 상향 분.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
웹(scratch-gui)에서 버튼 클릭으로 Link를 실행할 수 있도록 aluxlabs-link://
커스텀 URL 프로토콜을 등록한다.

- Package.appxmanifest: windows.protocol 확장으로 aluxlabs-link 스킴 등록
- Program.cs: 자동 생성 Main 대신 커스텀 진입점에서 Application.Start 이전에
  단일 인스턴스를 판정 — 비메인은 활성화를 메인으로 리다이렉트 후 종료하여
  트레이/:20211 중복 기동을 차단
- csproj: DISABLE_XAML_GENERATED_MAIN 컴파일 상수로 XAML 생성 Main 억제

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
버전 triplet을 한 곳(Version.props)으로 모으고, 손으로 관리하던
.appinstaller를 빌드 산출물(번들 파일명)에서 자동 생성하도록 한다.

- Version.props(신규): ReleaseTriplet 단일 소스. ScratchVersion.targets가
  git floor 대신 이 값을 우선 사용 → assembly/manifest/번들명 자동 일치
- AluxLabsLink.appinstaller.template(신규): 호스트/버전/번들 placeholder로
  prod/dev 두 개를 생성
- Makefile: show-version/set-version/release-patch·minor + appinstaller 타겟,
  sync-s3(-dev)이 staging 번들 기준으로 appinstaller 자동 생성·업로드
- 손으로 관리하던 dist/*.appinstaller 제거(템플릿 생성으로 대체)
- ReleaseTriplet 1.0.1 → 1.1.0 (딥링크: 하위호환 새 기능, minor)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
commit 934d4e2(버전 단일 소스화)로 .appinstaller가 자동 생성되고 수동
정본 파일이 제거됨에 따라, 낡은 '수동 3곳 편집' 안내를 갱신한다.

- §4: 커밋된 정본 → 템플릿 자동 생성으로 정정
- §4: git 태그 기반 버전 → Version.props 단일 소스(make set-version)
- §5: 릴리스 체크리스트를 make 기반(set-version → 빌드/서명 → sync-s3)으로

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
저사양 PC에서 클라이언트가 33ms write 주기를 못 지켜 1초 이상 TX가
끊기면 장치 RX 워치독(~1s)이 만료돼 경고음이 자주 울리던 문제 수정.

기존 RX 기반 가드 3종(RxYield 200 / TxIdle 900 / RxStall 500)은
워치독 대비 마진이 100ms뿐이고, RxStall 가드에 하드 데드라인이 없어
장치가 조용해지면 keep-alive가 무한 정지했다. 이를 단일
KeepAliveFeedIntervalMs(300ms) 바닥값으로 교체해, 마지막 TX(클라
write 또는 직전 resend) 이후 300ms가 지나면 RX 상태와 무관하게
재전송한다. 실효 워치독 ~900ms에 3배 여유라 저사양에서 틱이 늦어도
다음 발이 시간 안에 도착한다.

- keepAliveIntervalMs의 의미: 재전송 주기 → 폴링 간격으로 변경
- 타임스탬프 게이트로 대체된 ResetKeepAliveTimer / keepAliveIntervalMs
  필드 및 미사용 lastRxTicks 추적 제거
- 모든 keep-alive 장치에 일괄 적용 (codetinker 전용 아님)
- 관련 문서(SerialKeepAliveGuide, SerialApiReference) 동작 설명 갱신

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown


Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.


I have read the CLA Document and I hereby sign the CLA


0 out of 2 committers have signed the CLA.
@hakueon
@songtomtom
You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@hakueon hakueon closed this Jun 11, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 11, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants