Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
cc9b48b
feat(notro): refresh Discord limits, safe media packs, QA and Plane w…
writingdeveloper Oct 13, 2026
0a25920
ci: pin validated Ruff and sync Plane QA statuses
writingdeveloper Oct 13, 2026
5b14eba
chore(notro): prepare v2.11.0 candidate and track portfolio audit blo…
writingdeveloper Oct 13, 2026
4c26c5c
test(notro): verify pack dialogs and record green release build
writingdeveloper Oct 13, 2026
ea01c1e
feat(notro): harden independent Discord paste UX and native QA
writingdeveloper Oct 14, 2026
63db00a
fix(plane): preserve cross-session QA state and track native validation
writingdeveloper Oct 14, 2026
92f3d15
feat(notro): add opt-in GIF comparison and safe optimized paste
writingdeveloper Oct 14, 2026
668b117
ci(notro): retain short-lived unsigned installer for manual QA
writingdeveloper Oct 14, 2026
469de53
docs(notro): record verified unsigned QA installer provenance
writingdeveloper Oct 14, 2026
b0045e8
test(notro): exercise real Windows file dialogs and 5k WebView gallery
writingdeveloper Oct 14, 2026
e26122b
docs(notro): keep Discord draft E2E open after native Windows QA
writingdeveloper Oct 14, 2026
4ecf7bc
docs(notro): record secure signing options and main-branch gate
writingdeveloper Oct 14, 2026
43275b3
feat(notro): opt-in settings backup and reviewed restore without signing
writingdeveloper Oct 14, 2026
167e95c
chore(notro): enforce unsigned release readiness and real media QA
writingdeveloper Oct 17, 2026
82fd924
test(notro): exercise checksum-verified 2.10.3 to 2.11.0 upgrade
writingdeveloper Oct 17, 2026
413623c
fix(qa): avoid waiting on persistent tray child after old installer
writingdeveloper Oct 17, 2026
64ab145
docs(notro): record verified upgrade and manual release gates
writingdeveloper Oct 17, 2026
fa3aff1
feat(notro): capture owned native picker demo and isolate clipboard QA
writingdeveloper Oct 17, 2026
913352c
docs(notro): record native clipboard runner and owned demo evidence
writingdeveloper Oct 17, 2026
e0e0d70
feat(notro): guard focused clipboard paste and plan consent-led beta
writingdeveloper Oct 17, 2026
77689d6
fix(qa): use native ctypes HWND lookup on bare Windows runner
writingdeveloper Oct 17, 2026
6f2a7a6
docs(notro): record verified owned-window paste and beta research gates
writingdeveloper Oct 17, 2026
0f4606c
test(notro): verify installed EXE first-run GUI and defer Product Hunt
writingdeveloper Oct 17, 2026
d1b736c
docs(notro): verify installed EXE first-run and Product Hunt 2026 evi…
writingdeveloper Oct 17, 2026
461db39
feat(notro): add branded Windows tray popup and About with native fal…
writingdeveloper Oct 18, 2026
fd75562
docs(notro): verify packaged tray popup and About in hosted Windows QA
writingdeveloper Oct 18, 2026
affdfae
fix(notro): scale tray popup for Windows DPI and validate real tray e…
writingdeveloper Oct 18, 2026
8c210d5
test(notro): diagnose owned Win32 tray notification during installed QA
writingdeveloper Oct 18, 2026
0b81b17
test(notro): separate first welcome and returning-user tray event QA
writingdeveloper Oct 18, 2026
c7248b3
fix(notro): verify tray HWND ownership and record passing Win32 event QA
writingdeveloper Oct 18, 2026
1c5a730
docs(notro): close passing packaged systray QA and preserve launch bl…
writingdeveloper Oct 18, 2026
aeb8590
fix(notro): recover malformed data and unblock native first-use dialogs
writingdeveloper Oct 18, 2026
10f93fe
docs(notro): record verified installed recovery and first-use QA
writingdeveloper Oct 18, 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: 3 additions & 3 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ body:
attributes:
label: Notro version
description: Right-click the tray icon; the version is in the tooltip. Or check Settings → Apps.
placeholder: "2.10.1"
placeholder: "2.10.3"
validations:
required: true

Expand All @@ -40,7 +40,7 @@ body:
attributes:
label: Steps to reproduce
placeholder: |
1. Copy a 15 MB PNG
1. Copy a 24 MB PNG
2. Wait for the tray notification
3. Paste in Discord
validations:
Expand Down Expand Up @@ -78,5 +78,5 @@ body:
Close Notro, then run it once from a terminal with logging on:
`set NOTRO_DEBUG=%TEMP%\notro.log` followed by `"%LOCALAPPDATA%\Programs\Notro\Notro.exe"`.
Reproduce the problem, then paste the contents of `%TEMP%\notro.log` here.
The log contains window coordinates only — no clipboard content.
Review and redact the log before sharing; do not post account tokens, personal paths or private messages.
render: text
32 changes: 32 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
## Problem and user impact

Explain what is broken or missing, including a concrete scenario.

## Implementation

Describe the changed files, API/UI behavior and whether this changes
clipboard/Windows settings, network calls, user data or installed artifacts.

## Evidence (do not invent results)

- [ ] Python tests and Ruff passed on Windows
- [ ] Python dependency audit completed
- [ ] Media threshold preflight passed (if relevant)
- [ ] Native WebView2 test passed (if UI changed)
- [ ] Real Windows file dialogs tested (if dialog changed)
- [ ] Privacy, keyboard and accessibility reviewed
- [ ] All five languages and README instructions updated, or N/A
- [ ] Public Discord integration tested in a non-sending draft, or
**clearly marked NOT TESTED**
- [ ] Installer runtime/upgrade/uninstall checked, or clearly marked NOT TESTED
- [ ] No Discord client code modified, injected or bundled; no REA runtime use
- [ ] No sensitive user files, tokens or private media in commits/screenshots

## Release impact and known gaps

State what users can actually download now versus unreleased code.
Do not use CI success as a proxy for successful installation, Discord
account-policy compliance or permission to reuse marketing media.

**Release/public marketing is a separate maintainer action.**
Installers are unsigned by project decision; do not start signing workflows.
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,13 @@ jobs:
cache: pip
- name: Install dependencies
run: pip install -r requirements-dev.txt
- name: Audit build/runtime dependencies
run: |
pip install pip-audit==2.10.1
python -m pip_audit -r requirements-build.txt --strict
- name: Lint Python
run: ruff check notro_app tests scripts
- name: Release media threshold preflight
run: python scripts/qa_release_media_preflight.py
- name: Run tests
run: python -m pytest -q
67 changes: 66 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,23 @@ jobs:
runs-on: windows-latest
steps:
- uses: actions/checkout@v7
with:
# Required for trustworthy tagged-commit ancestry check.
fetch-depth: 0

- uses: actions/setup-python@v7
with:
python-version: "3.12"
cache: pip

- name: Install dependencies
run: pip install --upgrade pyinstaller -r requirements-dev.txt
run: pip install -r requirements-build.txt

- name: Audit build/runtime dependencies
run: python -m pip_audit -r requirements-build.txt --strict

- name: Release media threshold preflight
run: python scripts/qa_release_media_preflight.py

- name: Export app version
shell: pwsh
Expand All @@ -37,6 +46,16 @@ jobs:
}
"APP_VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append

- name: Verify release tag belongs to main
if: startsWith(github.ref, 'refs/tags/')
shell: pwsh
run: |
git fetch origin main --quiet
git merge-base --is-ancestor $env:GITHUB_SHA origin/main
if ($LASTEXITCODE -ne 0) {
throw "Refusing release: tagged commit is not in origin/main"
}

- name: Verify release tag matches app version
if: startsWith(github.ref, 'refs/tags/')
run: python check_release_version.py "$env:GITHUB_REF_NAME"
Expand All @@ -62,6 +81,29 @@ jobs:
- name: Run tests
run: python -m pytest -q

# Real own WebView2 windows, synthetic local images and no user data.
# Verifies first-use buttons and independent video dialog bridge.
- name: QA native welcome Try Picker and dismiss
if: github.event_name == 'workflow_dispatch'
run: python scripts/qa_native_welcome_flow.py

- name: QA native video confirm trim and cancel
if: github.event_name == 'workflow_dispatch'
run: python scripts/qa_native_video_dialog.py

# Accesses only disposable runner's clipboard, with synthetic media;
# never the maintainer's live Windows clipboard or Discord account.
- name: QA native Win32 clipboard on disposable runner
if: github.event_name == 'workflow_dispatch'
run: python scripts/qa_runner_clipboard_cf_hdrop.py

# The target window is owned by the disposable hosted runner only.
# This exercises PickerController -> Win32 CF_HDROP -> SendInput -> own
# Tk text field, never Discord or a user's live desktop/clipboard.
- name: QA own-window Win32 Ctrl+V and focus recovery
if: github.event_name == 'workflow_dispatch'
run: python scripts/qa_runner_owned_target_paste.py

# --collect-all webview가 numpy·cryptography까지 끌어온다 (앱은 둘 다 쓰지 않는다).
# build.bat과 같은 제외 플래그를 유지할 것 — 어긋나면 CI 산출물만 40MB 커진다.
- name: Build EXE
Expand All @@ -77,12 +119,35 @@ jobs:
run: |
& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" installer.iss /DAppVersion=$env:APP_VERSION

# Short-lived, unsigned QA in a fresh Windows GitHub-hosted runner.
# No installed client data or global clipboard are touched.
- name: QA install and uninstall on ephemeral runner
if: github.event_name == 'workflow_dispatch'
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
run: ./scripts/qa_installer_smoke.ps1 -ExpectedVersion $env:APP_VERSION

- name: Generate SHA256
shell: pwsh
run: |
$hash = (Get-FileHash dist/NotroSetup.exe -Algorithm SHA256).Hash.ToLower()
"$hash NotroSetup.exe" | Out-File -FilePath dist/NotroSetup.exe.sha256 -Encoding ascii -NoNewline

# On-demand QA build: short-lived UNSIGNED installer, no release/tag.
# Access depends on repository permissions; never market this as signed.
- name: Upload unsigned QA installer
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v7
with:
name: NOTRO-UNSIGNED-QA-${{ github.run_id }}
path: |
dist/NotroSetup.exe
dist/NotroSetup.exe.sha256
retention-days: 3
if-no-files-found: error
overwrite: false

# 태그 푸시일 때만 릴리스를 만든다. 덕분에 workflow_dispatch로 돌리면 빌드와
# installer 컴파일만 검증하고 릴리스는 만들지 않는다(installer.iss 안전 검증용).
- name: Publish release
Expand Down
90 changes: 90 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,96 @@
All notable changes to this project are documented in this file.
This project adheres to [Semantic Versioning](https://semver.org/).

## [2.11.0] - Unreleased (candidate: 2026-10-13)

### Added
- Windows notification-area right-click opens an owned Discord-inspired dark
WebView2 panel with picker shortcuts, monitoring controls and safe settings.
About displays the real program version, GitHub/Release/Issues, MIT,
platform and unsigned/independent disclosure. Native pystray menu and
About dialog remain available if WebView2 is missing or not yet ready.
The UI is localized to English, Korean, Japanese, Chinese and Spanish;
actual native WebView2 popup/screenshots were regression tested.
- Portable user-controlled ZIP emoji/sticker/GIF pack import/export with content
verification, size limits and duplicate detection. Private Captures and original
source URLs are excluded. This is **not** a full settings backup.
- First-run welcome offers a direct, explicit "Try picker" action.
- Added separate opt-in JSON preferences backup with explicit watched-folder
path disclosure, strict parsing, user review and two-step restoration.
No images/Captures, and auto-send and auto-capture are disabled on restore.
Real native Windows Save/Open with Korean filenames tested.

- On-demand release-workflow builds can retain a 3-day UNSIGNED QA installer
artifact with its SHA-256, enabling real Windows installer tests without
publishing a release/tag.
- Synthetic 100/1,000/5,000-item library performance benchmark, Windows regression
coverage and Ruff CI lint. REA static compatibility and marketing QA notes.

- Opt-in GIF size/quality preview: right-click an animated item to compare
original and optimized animations, frame timing, dimensions and file sizes.
A bounded Pillow engine tries palette reductions before lowering resolution,
preserves animation loop and frame timing, and rejects unsafe inputs.
Only the user-approved temporary candidate is pasted; automatic Enter is
suppressed. Original GIFs and library files remain unchanged.
- GIF preview lifecycle includes cancel/close cleanup and temporary localhost
media URLs. Native WebView2 fixture validates both images and a synthetic
approve/cancel flow without touching live Discord, user clipboard or chat.

### Fixed
- Native WebView2 Welcome and video confirmation dialogs: moved native
WinForms Window references into private API fields. Exposing the GUI
object through a public JavaScript bridge attribute caused recursive
AccessibilityObject traversal and could prevent first-use and video
buttons from becoming responsive. Actual native "Try Picker"/Dismiss,
video trim/mute/replan/confirm/close now pass.
- Corrupt or unsupported media ZIPs, nested invalid metadata and unsafe
settings JSON are rejected without mutating the media library. Bad
library.json/folders.json files are backed up byte-for-byte as local
.bak files so the application can recover without deleting cached media.
- ZIP archive outer size is bounded before reading central-directory
metadata, in addition to existing manifest and decompressed image caps.
- The Discord free upload default now matches the official August 2026 20 MB
limit. Existing explicit 10 MB choices remain available. The 95% safety margin
now targets **compressed output** rather than triggering needless compression
of 19–20 MB files.
- Watched folder JPEG files appear correctly beside PNG, GIF and WebP.
- Local media server streams GIF assets in bounded chunks; race handling returns
404 if a watched file vanishes before opening.
- Collection folder names cannot resolve to ancestor paths or Windows DOS devices.

### Notes
- Real Discord/WebView2 installer end-to-end QA and signing are **not yet complete**.
Do not publish this candidate release until the verification gates are met.

### Further hardening (release candidate; not yet published)
- Optional auto-send now requires a still-focused Discord desktop target and
checks the original window again before both paste and Enter. Ordinary
paste to other apps still works, but Enter is never automatic there. This
uses public Windows APIs only and cannot detect switching channels
inside one window; auto-send remains disabled by default.
- Local picker thumbnail URLs now require an unpredictable per-session
capability and the exact loopback Host header. ZIP manifest decompression
is bounded even for untrusted compressed input.
- Added isolated native Windows Edge WebView2 fixture smoke that verifies real
thumbnails, picker search/settings, all five language bridges and ZIP drop
import using synthetic data, without opening chats or modifying Discord.
- Paste safety: checks the original window BEFORE modifying the clipboard,
preserves any newer copy made while preparing media and verifies that
Windows accepted Ctrl+V key events before reporting success.
When focus is already lost, the clipboard is not changed. Included
five-language explanatory notifications and a separately owned
Win32 target-window QA harness on a disposable Windows runner.
- A single ZIP media pack may now be dragged onto the picker to import via
the same validated media pack path used by the explicit Import button.
- Added repeatable native Win32 Save As/Open dialog E2E on Korean ZIP
filenames and an isolated WebView2 fixture, including cancel paths.
- Measured synthetic 100/1,000/5,000-item Windows WebView2 DOM render and
search without touching user images. Real mixed GIF memory QA remains.
- Prefer current pywebview FileDialog enum with old-version-only fallback,
eliminating deprecated alias warnings.
- Plane backlog synchronization now preserves existing work-item state, priority
and ownership so restarting a session cannot silently reset QA progress.

## [2.10.3] - 2026-08-08

### Fixed
Expand Down
37 changes: 18 additions & 19 deletions CODE_SIGNING.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Code signing policy

> **Status: not signed yet.** Releases of Notro are currently unsigned, so Windows
> SmartScreen warns that the publisher is unknown. See
> [SECURITY.md](SECURITY.md#code-signing) for how to verify a download in the
> meantime. This document states the policy that governs signing, and is a
> prerequisite for applying to the [SignPath Foundation](https://signpath.org/)
> free certificate programme for open source projects.
> **Current policy (2026-10-14): no code signing planned.** The maintainer
> explicitly chose unsigned distribution. Windows SmartScreen may warn about
> an unknown publisher. See SECURITY.md for integrity checks and how to
> inspect/build from source. A SHA-256 file does not prove publisher identity.
> The historical SignPath investigation below is archived context, not an
> application or an instruction to pursue signing.

## Project

Expand All @@ -26,10 +26,18 @@ Notro is maintained by one person, who therefore holds all three roles:
|---|---|
| Author — writes and modifies code | [@writingdeveloper](https://github.com/writingdeveloper) |
| Reviewer — approves changes before they reach `main` | [@writingdeveloper](https://github.com/writingdeveloper) |
| Approver — authorises a release to be signed | [@writingdeveloper](https://github.com/writingdeveloper) |
| Release approver — reviews unsigned binary and SHA-256 before publication | [@writingdeveloper](https://github.com/writingdeveloper) |

Enable multi-factor authentication on the GitHub maintainer account.

## Historical signing research (not in scope)

Signing is deliberately deferred. Do not initiate SignPath/Azure accounts,
request certificates or alter branch restrictions solely for code signing.
Independent CI/security QA and release authorization still matter.
The archived research note is at
docs/research/2026-10-13-notro-code-signing-readiness.md.

Multi-factor authentication is required on the GitHub account and on the signing
service account.

## How releases are built

Expand All @@ -43,7 +51,7 @@ beside it. Anyone can reproduce a build from source with

## Binary metadata

Every signed artifact carries consistent product and version metadata:
Every unsigned release artifact still carries consistent product and version metadata:

- `Notro.exe` — `ProductName: Notro`, `ProductVersion`/`FileVersion` generated from
`notro_app.__version__` by [`build_version_file.py`](build_version_file.py).
Expand All @@ -70,12 +78,3 @@ circumvent any security measure. It prepares the Windows clipboard and simulates
local <kbd>Ctrl</kbd>+<kbd>V</kbd> — the same class of input automation as the
built-in Windows emoji panel. It does not modify, patch, or inject into the Discord
client, and never accesses an account or token.

<!--
Once a certificate is granted, add here:

## Attribution

Free code signing is provided by [SignPath.io](https://signpath.io/), certificate
by [SignPath Foundation](https://signpath.org/).
-->
Loading