From 195edb6a29e2daa2486aec314f82bb21f2aef104 Mon Sep 17 00:00:00 2001 From: Cody LeCates Date: Fri, 6 Nov 2026 16:29:23 -0700 Subject: [PATCH 1/3] Build reviewed workflow intake reports and prepare Windows release evidence --- .github/workflows/dotnet.yml | 39 +++- AGENTS.md | 14 +- PRIVACY.md | 33 +++- README.md | 21 ++- docs/AppLens-Privacy-Draft.md | 17 +- docs/AppLens-Product-Vision.md | 26 ++- docs/AppLens-Support-Draft.md | 26 +-- docs/AppLens-v1-Handoff.md | 46 +++++ docs/AppLensDesktop-Build.md | 15 +- docs/Store-Listing-Draft.md | 28 +-- docs/Store-Readiness-Checklist.md | 10 +- src/AppLens.Backend/InventorySnapshot.cs | 4 +- src/AppLens.Backend/PreparedIntakeReport.cs | 30 ++++ src/AppLens.Backend/ReportWriter.cs | 66 +++++-- src/AppLens.Backend/WorkflowIntake.cs | 53 ++++++ src/AppLens.Desktop/MainWindow.xaml | 21 ++- src/AppLens.Desktop/MainWindow.xaml.cs | 119 +++++++++++-- src/AppLens.Desktop/Package.appxmanifest | 2 +- src/AppLens.Desktop/ReportShareService.cs | 70 ++++++++ .../WorkflowIntakeTests.cs | 167 ++++++++++++++++++ 20 files changed, 719 insertions(+), 88 deletions(-) create mode 100644 src/AppLens.Backend/PreparedIntakeReport.cs create mode 100644 src/AppLens.Backend/WorkflowIntake.cs create mode 100644 src/AppLens.Desktop/ReportShareService.cs create mode 100644 tests/AppLens.Backend.Tests/WorkflowIntakeTests.cs diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index b4eeab7..984a508 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -13,6 +13,7 @@ jobs: build-test: name: Build and test runs-on: windows-latest + timeout-minutes: 25 steps: - name: Checkout @@ -30,7 +31,16 @@ jobs: run: dotnet build AppLensDesktop.sln --configuration Release --no-restore - name: Test - run: dotnet test AppLensDesktop.sln --configuration Release --no-restore + run: dotnet test AppLensDesktop.sln --configuration Release --no-build --no-restore --logger trx --results-directory artifacts/test-results + + - name: Retain Windows test evidence + if: always() + uses: actions/upload-artifact@v4 + with: + name: windows-test-results-${{ github.sha }} + path: artifacts/test-results/*.trx + if-no-files-found: warn + retention-days: 14 - name: Test Windows initial snapshot shell: powershell @@ -40,3 +50,30 @@ jobs: shell: powershell run: .\tests\Script.Tests\Test-StoreConfiguration.ps1 + - name: Build unsigned Store candidate + shell: pwsh + run: | + $candidate = Join-Path $env:GITHUB_WORKSPACE 'artifacts/release-candidate' + dotnet publish src/AppLens.Desktop/AppLens.Desktop.csproj -c Release -p:Platform=x64 -p:GenerateAppxPackageOnBuild=true -p:AppxPackageSigningEnabled=false "-p:AppxPackageDir=$candidate/msix/" "-p:AppxPackageTestDir=$candidate/install/" + if ($LASTEXITCODE -ne 0) { throw 'Unsigned candidate build failed.' } + $bundles = @(Get-ChildItem "$candidate/install" -Recurse -Filter '*.msixbundle') + if ($bundles.Count -ne 1) { throw 'Expected exactly one unsigned candidate bundle.' } + [ordered]@{ + commit = '${{ github.sha }}' + run = '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' + status = 'Unsigned candidate; installation, sharing and certification are not verified' + file = $bundles[0].Name + bytes = $bundles[0].Length + sha256 = (Get-FileHash $bundles[0].FullName -Algorithm SHA256).Hash + } | ConvertTo-Json | Set-Content "$candidate/candidate.json" -Encoding utf8 + + - name: Retain unsigned candidate for acceptance + uses: actions/upload-artifact@v4 + with: + name: installed-unsigned-candidate-${{ github.sha }} + path: | + artifacts/release-candidate/install/**/*.msixbundle + artifacts/release-candidate/candidate.json + if-no-files-found: error + retention-days: 14 + diff --git a/AGENTS.md b/AGENTS.md index 4e55599..e23a752 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,8 +1,10 @@ # AppLens Agent Instructions -Installed (formerly AppLens) v1 is a lightweight Windows client for guided screen-share sessions: -scan -> review/filter apps and storage -> approve supported uninstalls -> verify -results -> save a local report. Keep communication concise and plain-English. +Installed (formerly AppLens) v1 is a lightweight Windows client for workflow-consultation intake: +open -> read-only scan -> review PC specifications and installed tools -> share a +report with the consultant. Optional client goals, services and constraints are +statements, not scanned facts. Existing confirmed uninstall/history features remain +secondary. Keep communication concise and plain-English. The Store product is Installed (9PBPFLL6JVVV). Keep AppLens repository, namespaces, legacy storage paths and DPAPI purpose identifiers stable; renaming those can @@ -20,9 +22,11 @@ break history compatibility. AppLens-Tune remains a separate deferred component. - Scans are read-only. System changes require explicit confirmation, supported mechanisms and recorded outcomes. Preserve Windows approval/policy controls; do not add automatic remediation, forced deletion or automatic restarts. -- Keep the approved local-only privacy design: no accounts, telemetry or uploads; +- Keep the approved local-only privacy design: no accounts, telemetry or automatic uploads; Windows current-user protection for new history without plaintext duplicates; - readable reports redacted by default. Preserve legacy evidence unchanged. + readable reports redacted by default. User-reviewed reports may be handed to + Windows sharing; the user chooses the app and recipient. Never claim delivery + from opening the share UI. Preserve legacy evidence unchanged. ## Working rules diff --git a/PRIVACY.md b/PRIVACY.md index 064bb0f..b2322d4 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -1,11 +1,32 @@ -# AppLens-desktop Privacy Notice +# Installed desktop privacy notice -AppLens-desktop is local-first and operator-controlled. Scanner and default diagnostic scans collect local workstation inventory and readiness evidence without changing the machine. +Installed (the AppLens Windows client) collects PC specifications and installed +application information locally. A read-only scan starts when you open the app; +you can cancel it or start another scan. The scan does not change system settings. -Tune actions can change system state only after explicit user approval, through narrow executor paths, and with blackboard records for the proposal, approval, execution result, and follow-up verification. The app does not upload data, create accounts, or run background monitoring. Reports are exported only when the user chooses to export them. +Collected information includes Windows version, manufacturer/model, processor and +graphics names, memory and disk readings, installed app names/versions/publishers, +installation identities and locations, computer name and Windows username. +Optional workflow goals, services and constraints are text you supply. Do not +include passwords or access keys. The client does not collect credentials or run +the deferred AppLens-Tune diagnostics. It has no accounts, telemetry, automatic +uploads or background monitoring. -Scans may include installed app names, app versions, computer name, Windows username, OS details, top processes, startup entries, selected service states, storage hotspot paths/sizes, repo placement paths, and local tool summaries for WSL, Docker, and Ollama when available. +Reports include the complete captured inventory, optional notes and recorded +actions even when display filters hide rows. Known computer/user identifiers and +the current user's profile path are redacted by default; you may explicitly +include them. App names and notes can still contain sensitive information. -Default exports redact user, computer, and profile path details. Users may explicitly choose to include raw local details when exporting. +Download report writes a readable file only to a destination you select. Send +report first shows a review, then hands that report to Windows sharing. You choose +the sharing app and recipient; their handling of the report is subject to their +own practices. Installed does not choose a recipient or verify email delivery. +The app supplies a streamed attachment without writing an additional export file; +Windows and the selected sharing app may cache or retain the shared content. -For production Microsoft Store submission, replace this file with a hosted privacy policy URL owned by CSI. +Supported uninstall actions require separate confirmation. New action history is +protected for the current Windows user; older development history is preserved. +Exported/shared reports are readable and are not protected by that history +encryption. See the [full privacy draft](docs/AppLens-Privacy-Draft.md) for history +and retention limitations. The release requires a verified hosted policy and +contact details; this repository notice is not evidence of Store publication. diff --git a/README.md b/README.md index 262c38a..ea28900 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

AppLens

- CSI's local control board for managing workstation apps, agents, evidence, and approvals. + Understand a client's PC and installed tools before designing their workflow.

@@ -16,11 +16,14 @@ ## Overview -AppLens is CSI's mobile-OS-inspired local control board. It gives a workstation one place to see apps, agents, evidence, proposed actions, and operator approvals. +AppLens is CSI's local technical intake tool for workflow consultations. A client +opens the Windows app, reviews their PC specifications and installed tools, then +shares the report with their consultant. The Store product name is **Installed**. -The first Store release is now centered on a quick client-session workflow: -install AppLens, scan, review a sortable app table, approve supported uninstalls, -and verify the result. Its approved launch target is **Windows 11 x64**; Windows 10 +The first Store release centers on **open → scan → review → send a workflow intake +report**. Optional client goals, services and constraints complement the scan. +Existing confirmed uninstalls and verification remain available. Its approved +launch target is **Windows 11 x64**; Windows 10 and native ARM64 support are deferred. The scripts/platform work below has a broader scope than this Store client. See [Product Vision](docs/AppLens-Product-Vision.md) for the current product direction and proposed client design. The broader platform below @@ -46,15 +49,19 @@ AppLens is local-first and operator-controlled: - read-only scans by default - approval-gated actions - no automatic remediation without an explicit grant -- no telemetry, accounts, or cloud upload +- no telemetry, accounts, or automatic upload - user-controlled report export +- reviewed report sharing through the user's chosen Windows sharing app - default report redaction for user, machine, and profile-path details ## AppLens-desktop AppLens is the WinUI 3 inventory client. It provides storage and device readings, a searchable/sortable app table, confirmed Windows/vendor uninstall routes, -verification, local action history, and full JSON/Markdown/HTML downloads. +verification, local action history, and full JSON/Markdown/HTML downloads. It +starts a read-only scan on opening and puts workflow intake facts first. Send +report reviews a frozen export before opening Windows sharing; it does not verify +email delivery. Download report is available when a sharing app is unavailable. See the [uninstall route contract](docs/AppLens-Uninstall-Routes.md) and [Store readiness](docs/Store-Readiness-Checklist.md) for verification limits. diff --git a/docs/AppLens-Privacy-Draft.md b/docs/AppLens-Privacy-Draft.md index f77d860..8b17371 100644 --- a/docs/AppLens-Privacy-Draft.md +++ b/docs/AppLens-Privacy-Draft.md @@ -1,9 +1,12 @@ # Installed privacy policy — publication draft -Installed collects information locally when you start a scan. This includes installed +Installed starts a read-only local scan when you open the app; you can cancel it +or start another scan. It collects installed app names, versions, publishers, installation identities and locations, reported sizes, removal information, local disk capacity and usage, Windows/device details, processor and graphics names, memory usage, uptime, computer name and Windows username. +Optional workflow goals, online services and constraints are statements you enter, +not facts verified by the scan. Do not include passwords or access keys. Installed v1 does not collect top-process rankings or run the deferred AppLens-Tune diagnostics. It does not include accounts, telemetry, automatic uploads, cloud @@ -17,13 +20,17 @@ completion can leave an unknown outcome; review it in Windows and run a fresh sc Uninstalling an app can delete that app's data. Installed does not provide an undo facility for third-party uninstalls. -Reports are saved only when you choose a local destination. They contain the full +Downloaded reports are saved only when you choose a local destination. They contain the full captured inventory and recorded actions, including entries hidden by display filters. Computer/user identifiers and profile paths are redacted by default. -You can explicitly include personal identifiers. Redaction is not a guarantee +Reports also contain your optional notes. You can explicitly include personal identifiers. Redaction is not a guarantee that a report contains no sensitive information; review it before sharing. -Installed does not send the report to a consultant or AI service. Any sharing you -perform afterward is your choice and is subject to the recipient's practices. +Send report shows a review before handing a frozen report to Windows sharing. +You choose a sharing app and recipient and complete sending there. Installed +neither chooses the recipient nor verifies delivery. It supplies the attachment +as a stream without creating an extra export file; Windows and the selected app +may cache or retain the content. Sharing is subject to that app's and recipient's +practices. Download report remains available for manual attachments. Local history and saved reports remain on your device until removed by you or by Windows' applicable app-data handling. Exported files are separate from the app. diff --git a/docs/AppLens-Product-Vision.md b/docs/AppLens-Product-Vision.md index 76c838a..b3b8dc3 100644 --- a/docs/AppLens-Product-Vision.md +++ b/docs/AppLens-Product-Vision.md @@ -1,7 +1,24 @@ -# AppLens v1: executable task list +# AppLens v1: workflow consultation intake -Build a lightweight Microsoft Store app for a guided screen-share session: -**scan -> review apps and storage -> approve removals -> download results for AI review.** +Build a lightweight Microsoft Store app that answers a consultant's request for +the client's PC setup: **open -> scan -> review -> send a workflow intake report.** + +The consultant uses the report to architect workflows around the client's hardware, +operating system, installed applications and runtimes. It supplies observations, +not an automatic compatibility verdict. Optional goals, online services and +constraints are labeled client statements. Missing readings and coverage stay +visible; installed software does not prove licensing, sign-in or working access. + +The Windows client begins a read-only scan on opening. Review report shows a +plain-language summary with the complete export available to inspect; Send report +opens review before handing the frozen file to Windows +sharing. The client chooses a sharing app and recipient. Download report remains +available for email attachments and AI review in Markdown, JSON and HTML. No email +credentials, fixed recipient or automatic delivery are introduced. Existing +confirmed removal and history features remain available as secondary workflows. + +Multi-device collection over SSH and remote execution remain future scope. This +change does not add Linux collector parity or publish a Store package. ## AppLens v1 implementation boundary @@ -14,7 +31,8 @@ Build a lightweight Microsoft Store app for a guided screen-share session: - Use a silver, restrained metallic client with crisp edges, matte-black type, one main app table, and secondary device details/action history. - Keep scans read-only and local. No accounts, telemetry, automatic uploads, - automatic remediation, forced deletion, or automatic restarts. + automatic remediation, forced deletion, or automatic restarts. Explicit, + user-reviewed Windows sharing is a handoff to the user's selected app. - Preserve existing AppLens-Tune in `future/AppLens-Tune/`; core v1 must have no runtime or build dependency on it. Keep shared infrastructure in the backend. Defer startup/service optimization, top-process rankings, deeper diagnostics, diff --git a/docs/AppLens-Support-Draft.md b/docs/AppLens-Support-Draft.md index 6b3319a..40f194e 100644 --- a/docs/AppLens-Support-Draft.md +++ b/docs/AppLens-Support-Draft.md @@ -1,17 +1,23 @@ # Installed support — publication draft -Installed v1 is a local Windows app for inventory, storage review, supported removal -actions and report download. It is not a background optimizer. +Installed v1 is a local Windows app that prepares PC specifications and installed +tools for a workflow consultation. Supported removal and history features are +also available. It is not a background optimizer. ## Using Installed -1. Select Scan this PC. -2. Review storage and the app table. Search or filter the rows as needed. -3. Review the exact app and scope before confirming an uninstall. Complete any - Windows or vendor approval dialog. -4. Check action history and refresh results. A Settings handoff or process exit - alone is not proof of removal. -5. Select Download report and choose Markdown, JSON or HTML. The complete capture - is included even when table filters are active. +1. Open Installed. A read-only scan starts automatically; Scan this PC refreshes it. +2. Review your PC summary and installed tools. Optionally add your workflow goal, + online services and constraints in the panel below the app table. +3. Select Review report. Inspect the summary and expand Complete report if needed. +4. Select Send report, review it, then choose a sharing app and recipient in + Windows. Finish sending in that app; Installed cannot confirm email delivery. +5. If your email app is unavailable in Windows sharing, select Download report + and attach the file manually. Markdown, JSON and HTML all contain the complete + capture even when table filters hide rows. + +For an uninstall, review the exact app and scope before confirming. Complete any +Windows or vendor approval, then inspect action history and refreshed results. +A Settings handoff or process exit alone is not proof of removal. Unknown size means the installer did not report a usable value. It does not mean zero space. Observed disk changes can include other activity. diff --git a/docs/AppLens-v1-Handoff.md b/docs/AppLens-v1-Handoff.md index e7009f4..9b98f10 100644 --- a/docs/AppLens-v1-Handoff.md +++ b/docs/AppLens-v1-Handoff.md @@ -1,5 +1,51 @@ # AppLens v1 handoff to COP-241 — updated 2026-09-18 +## Workflow intake implementation — 2026-11-06 + +The owner clarified the primary use: a client sends their PC setup to a consultant +who uses it to architect workflows. Implementation in the supplied Linux source +copy at `/home/cody/HULK/projects/applens/AppLens-main` targets the existing Windows +11 client. Store identity, namespaces, protected history and removal mechanisms +remain intact. No publication, email delivery or Linux scanner changes occurred. + +The client now starts one read-only scan on opening, shows PC specifications and +installed-tool counts, accepts optional goals/services/constraints, and offers +Review report and Send report. Review uses a readable summary with the complete +export available underneath. Sharing freezes the reviewed report, passes a +streamed file to Windows sharing, and leaves app/recipient selection to the user. +It never reports verified delivery. Download report remains the email-attachment +fallback. Markdown, HTML and JSON retain full inventory and coverage, independent +of table filters. Schema 2.1 adds client context and a derived workflow intake +summary; client statements are distinct from observations and workload readiness +is not inferred. JSON redaction now preserves field names; HTML/Markdown redact +before escaping, and Markdown notes cannot introduce active links or images. + +Verification on Linux with a temporary .NET 10 SDK: + +- 43 selected backend checks passed, including all 12 new intake checks covering + partial/unknown readings, context, redaction, literal notes and frozen reports. +- 17 desktop presentation checks passed. Two existing presentation checks and one + backend metadata check require `shell32.dll`; their attempted runs failed on + Linux. The backend failure also reproduces in the untouched source baseline. +- All desktop C# sources compiled against actual Windows API references in a + temporary compile-only harness with XAML field declarations. XAML XML and event + handler names were checked. This is not a native WinUI build or launch. +- The real desktop build stops at `XamlCompiler.exe` on Linux (exec format error). + Full Windows regression, rendering, startup and share-target behavior remain + unverified. The referenced Windows shared-standards files were unavailable here. + +Evidence is under `/tmp/applens-intake-tests`; the untouched 155-file baseline is +under `/tmp/applens-intake-before`. These are local temporary evidence paths. No +usable Git metadata was exposed in this source copy, so no commit/push was made. + +Before client delivery, run the normal Release build and complete test suite on +Windows 11. Exercise initial scan, cancellation/rescan, optional notes, partial +readings, readable/complete review, redacted/raw exports, cancelled and repeated +sharing, unavailable email targets, and saved attachments. Confirm actual received +file contents using a disposable local test target. Store and clean-PC acceptance +remain subject to the release requirements below; no installable release was +produced by this Linux session. + ## Current release preparation — 2026-11-04 The owner reviewed the redesigned local client and requested GitHub delivery, diff --git a/docs/AppLensDesktop-Build.md b/docs/AppLensDesktop-Build.md index b7ece0e..8ea8d4e 100644 --- a/docs/AppLensDesktop-Build.md +++ b/docs/AppLensDesktop-Build.md @@ -1,8 +1,9 @@ # AppLens desktop build The active WinUI 3 client scans installed apps, disks and basic device information, -presents a sortable table, supports confirmed removal routes, and saves reports -locally. It has no build/runtime dependency on future/AppLens-Tune. +presents a workflow intake summary and sortable table, supports confirmed removal +routes, and saves or shares reviewed reports. It has no build/runtime dependency +on future/AppLens-Tune. ## Build and run The approved first-release target is Windows 11 x64 (minimum build 22000). @@ -26,9 +27,15 @@ A direct project build without Platform can use a different output directory. Debug explicitly before using that launcher to review source changes. ## Using the client -Alt+S starts a scan; Alt+D opens Download report after completion. Tab/Shift+Tab +Opening the app starts one read-only scan. Alt+S starts a new scan; Alt+D opens +Download report after completion. Tab/Shift+Tab move through controls and row actions. Choose Markdown, JSON or HTML before -downloading. The Windows picker starts in Downloads; choose another folder if +downloading or sharing. Optional client goals, online services and constraints +are included as unverified client statements. Review report shows a plain-language +summary and an expandable complete export. Send report requires review, then opens Windows +sharing so the user chooses an app and recipient. If no suitable email/share app +is available, download the report and attach it manually. Opening sharing is not +proof of delivery. The Windows picker starts in Downloads; choose another folder if needed. Cancelling keeps the results. Exports include the complete scan even when filters hide rows, and personal identifiers are redacted by default. diff --git a/docs/Store-Listing-Draft.md b/docs/Store-Listing-Draft.md index d827129..4b5840f 100644 --- a/docs/Store-Listing-Draft.md +++ b/docs/Store-Listing-Draft.md @@ -9,13 +9,15 @@ Installed. Reserved Store ID: `9PBPFLL6JVVV`; publisher: Copper State Intelligen The repository, assemblies and legacy storage paths retain the AppLens name. ## Short description -Review installed apps and disk usage, approve supported uninstalls, and download a local report. +Share your PC specifications and installed tools to plan a workflow with your consultant. ## Description -Installed puts your Windows 11 app inventory and storage usage in one simple table. -Run a local scan, search apps or publishers, and sort by name or reported size. -See installation scope, removal restrictions, disk capacity and free space, plus -secondary Windows, processor, graphics and memory details. +Installed prepares a local snapshot of your Windows 11 PC for a workflow +consultation. Open the app to collect PC specifications, storage and installed +tools. Add optional goals, online services or requirements, review the report, +then choose how to share it with your consultant through Windows sharing. +Installed software does not prove a license, sign-in or working service access; +unknown readings and scan coverage remain visible. Uninstall eligible desktop and current-user Store apps after confirming the exact installation. Windows or the vendor may request administrator approval. Installed @@ -24,11 +26,13 @@ App data can be deleted by an uninstall; save your work and review the confirmat Download the full inventory and action history as Markdown, JSON or HTML for review or sharing. Personal identifiers and profile paths are redacted by -default, with an explicit option to include them. Installed saves locally; you -choose whether and how to send the report. +default, with an explicit option to include them. You choose the sharing app and +recipient. If your email app is unavailable, save the report and attach it manually. ## Features - Local app inventory and disk total/used/free readings. +- PC specification summary and optional workflow context. +- Review before sharing, with Markdown, JSON and HTML report downloads. - Search and size/name/publisher sorting; scope/type/removal filters. - Explicit supported uninstall actions, Windows approval, and result verification. - Local action history and downloadable reports. @@ -55,8 +59,8 @@ availability before submission rather than implying the x64 package blocks it. - Review final screenshots and artwork before submission. ## Screenshot subjects -The finished app table, largest-first storage review, exact-target confirmation, -and download/history controls. Use reviewed screenshots without personal data; +The PC summary, optional workflow context, reviewed report and sharing controls, +plus the app table and secondary removal/history flow. Use reviewed screenshots without personal data; internal development captures are not automatically Store listing assets. ## Certification notes @@ -67,7 +71,11 @@ Installed itself remains unelevated; Windows handles explicit administrator handoffs. Explain and demonstrate each supported removal route in certification. ### Reviewer walkthrough (final identity/build to be supplied) -No login, account or server is required. Launch Installed and select Scan this PC. +No login, account or server is required. Launch Installed; a read-only scan starts. +Inspect the PC summary, add disposable sample workflow notes, then review and +save a redacted report. Send report opens review before Windows sharing. Cancel +without sending, or use a controlled test target to inspect the received file. +Opening the sharing chooser is not proof of delivery. Search by app/publisher, choose Largest first, and inspect an app's scope/details. Unknown sizes mean unavailable metadata, not zero disk usage. diff --git a/docs/Store-Readiness-Checklist.md b/docs/Store-Readiness-Checklist.md index e40140f..401f1f7 100644 --- a/docs/Store-Readiness-Checklist.md +++ b/docs/Store-Readiness-Checklist.md @@ -1,7 +1,8 @@ # Microsoft Store readiness -The active v1 flow is inventory -> storage/app table -> approved removal -> -verification -> local report. AppLens-Tune is preserved outside the core build. +The active v1 flow is open -> read-only scan -> review PC/tools and optional +workflow context -> share or save a report. Supported removal and history remain +secondary. AppLens-Tune is preserved outside the core build. The implementation plan is [Product Vision](AppLens-Product-Vision.md); changing work status is tracked in Linear, not duplicated here. For completed source checks and a condensed keep/defer list of hands-on work, @@ -51,6 +52,11 @@ this is a tooling limit, not a renewed owner pause. - [ ] Complete the installed customer flow on a clean standard-user Windows PC, including native/package dependencies and offline scan/export. +- [ ] Verify automatic initial scan and cancellation, optional workflow notes, + summary/full-report review, redacted and raw exports, cancelled/repeated + Windows sharing, a controlled received-file comparison, and the attachment + fallback when the email app is not listed. Check focus, text scaling and + small-window layout for the new summary, notes and review controls. - [ ] Complete and verify desktop-shortcut creation and double-click launch, reopening without developer tools, and a controlled same-identity update preserving launch access and readable history. diff --git a/src/AppLens.Backend/InventorySnapshot.cs b/src/AppLens.Backend/InventorySnapshot.cs index 32f881e..0368385 100644 --- a/src/AppLens.Backend/InventorySnapshot.cs +++ b/src/AppLens.Backend/InventorySnapshot.cs @@ -3,7 +3,7 @@ namespace AppLens.Backend; ///

The core client/report contract; contains no deferred diagnostics. public sealed class InventorySnapshot { - public string SchemaVersion { get; init; } = "2.0"; + public string SchemaVersion { get; init; } = "2.1"; public DateTimeOffset GeneratedAt { get; init; } = DateTimeOffset.Now; public DeviceSummary Machine { get; init; } = new(); public InventorySummary Inventory { get; init; } = new(); @@ -11,6 +11,8 @@ public sealed class InventorySnapshot public TimeSpan Duration { get; init; } public TimeSpan? FirstResultsDuration { get; init; } public List Actions { get; init; } = []; + public WorkflowClientContext ClientContext { get; init; } = new(); + public WorkflowIntakeSummary WorkflowIntake => WorkflowIntakeSummary.From(this); public bool IsPartial => ProbeStatuses.Any(p => p.State != ProbeState.Succeeded); [System.Text.Json.Serialization.JsonIgnore] public IEnumerable Applications => Inventory.DesktopApplications diff --git a/src/AppLens.Backend/PreparedIntakeReport.cs b/src/AppLens.Backend/PreparedIntakeReport.cs new file mode 100644 index 0000000..c80868b --- /dev/null +++ b/src/AppLens.Backend/PreparedIntakeReport.cs @@ -0,0 +1,30 @@ +namespace AppLens.Backend; + +public enum IntakeReportFormat { Markdown, Json, Html } + +/// Freeze the report before review so sharing cannot substitute a later scan or edit. +public sealed record PreparedIntakeReport(string FileName, string Extension, string Content, string ReviewSummary, + bool IncludesPersonalIdentifiers) +{ + public static PreparedIntakeReport Create(InventorySnapshot snapshot, IntakeReportFormat format, + bool includePersonalIdentifiers = false) + { + var writer = new ReportWriter(); + var (extension, content) = format switch + { + IntakeReportFormat.Json => (".json", writer.WriteJson(snapshot, includePersonalIdentifiers)), + IntakeReportFormat.Html => (".html", writer.WriteHtml(snapshot, includePersonalIdentifiers)), + _ => (".md", writer.WriteMarkdown(snapshot, includePersonalIdentifiers)) + }; + var intake = snapshot.WorkflowIntake; + var context = snapshot.ClientContext; + var summary = $"Captured: {snapshot.GeneratedAt:g}\n{intake.Coverage}\n\n{intake.ToDisplayText()}\n\n" + + $"Your notes (not independently verified)\nGoal: {Response(context.Goal)}\n" + + $"Tools / services: {Response(context.Services)}\nConstraints: {Response(context.Constraints)}"; + if (!includePersonalIdentifiers) summary = new RedactionService().Redact(summary, snapshot.Machine); + return new($"Installed-Workflow-{snapshot.GeneratedAt:yyyyMMdd-HHmmss-fff}{extension}", + extension, content, summary, includePersonalIdentifiers); + } + + private static string Response(string text) => string.IsNullOrWhiteSpace(text) ? "Not provided" : text.Trim(); +} diff --git a/src/AppLens.Backend/ReportWriter.cs b/src/AppLens.Backend/ReportWriter.cs index 8816650..f5f488e 100644 --- a/src/AppLens.Backend/ReportWriter.cs +++ b/src/AppLens.Backend/ReportWriter.cs @@ -1,5 +1,6 @@ using System.Text; using System.Text.Json; +using System.Text.Json.Nodes; namespace AppLens.Backend; @@ -9,32 +10,38 @@ public sealed class ReportWriter public ReportWriter() : this(new RedactionService()) { } public ReportWriter(RedactionService redaction) => _redaction = redaction; - public string WriteJson(InventorySnapshot snapshot, bool includeRawDetails = false) => - Protect(JsonSerializer.Serialize(snapshot, new JsonSerializerOptions { WriteIndented = true }), snapshot, includeRawDetails); + public string WriteJson(InventorySnapshot snapshot, bool includeRawDetails = false) + { + var document = JsonSerializer.SerializeToNode(snapshot)!; + if (!includeRawDetails) RedactValues(document, snapshot); + return document.ToJsonString(new JsonSerializerOptions { WriteIndented = true }); + } public string WriteMarkdown(InventorySnapshot snapshot, bool includeRawDetails = false) { - var text = new StringBuilder("# Installed inventory report\n\n"); + var text = new StringBuilder("# Installed workflow intake report\n\n"); + text.AppendLine("A snapshot to help your consultant design a workflow around this PC and its installed tools."); text.AppendLine("Reported sizes are estimates, not guaranteed reclaimable space. Disk changes are observations, not attribution."); foreach (var section in Sections(snapshot)) { text.AppendLine($"\n## {section.Title}\n"); text.AppendLine("| " + string.Join(" | ", section.Headers.Select(Cell)) + " |"); text.AppendLine("| " + string.Join(" | ", section.Headers.Select(_ => "---")) + " |"); - foreach (var row in section.Rows) text.AppendLine("| " + string.Join(" | ", row.Select(Cell)) + " |"); + foreach (var row in section.Rows) text.AppendLine("| " + string.Join(" | ", row.Select(value => Cell(Protect(value, snapshot, includeRawDetails)))) + " |"); } - return Protect(text.ToString(), snapshot, includeRawDetails); + return text.ToString(); } public string WriteHtml(InventorySnapshot snapshot, bool includeRawDetails = false) { var html = new StringBuilder(""" - Installed inventory report + Installed workflow intake report

Installed inventory report

+ th{background:#e7e9ec}h2{margin-top:2rem}

Installed workflow intake report

+

A snapshot to help your consultant design a workflow around this PC and its installed tools.

Reported sizes are estimates, not guaranteed reclaimable space. Disk changes are observations, not attribution.

"""); foreach (var section in Sections(snapshot)) @@ -43,11 +50,11 @@ public string WriteHtml(InventorySnapshot snapshot, bool includeRawDetails = fal foreach (var header in section.Headers) html.Append($"{Formatting.Html(header)}"); html.Append(""); foreach (var row in section.Rows) - html.Append("" + string.Join("", row.Select(c => $"{Formatting.Html(c)}")) + ""); + html.Append("" + string.Join("", row.Select(c => $"{Formatting.Html(Protect(c, snapshot, includeRawDetails))}")) + ""); html.Append(""); } html.Append(""); - return Protect(html.ToString(), snapshot, includeRawDetails); + return html.ToString(); } public async Task WriteAllAsync(InventorySnapshot snapshot, string directory, bool includeRawDetails, @@ -69,10 +76,21 @@ private sealed record Section(string Title, string[] Headers, IEnumerable Sections(InventorySnapshot snapshot) { var machine = snapshot.Machine; + var intake = snapshot.WorkflowIntake; + yield return new("Workflow intake", ["Field", "Observation"], + intake.Facts.Select(f => new[] { f.Label, f.Value }).Prepend(["Coverage", intake.Coverage])); + yield return new("Client context — provided by the client, not verified", ["Question", "Client response"], new[] + { + new[] { "What would you like to accomplish?", Response(snapshot.ClientContext.Goal) }, + new[] { "Which tools or online services do you use?", Response(snapshot.ClientContext.Services) }, + new[] { "Any constraints or requirements?", Response(snapshot.ClientContext.Constraints) } + }); + yield return new("Planning boundaries", ["What this report does not establish"], + intake.PlanningLimits.Select(limit => new[] { limit })); yield return new("Capture", ["Field", "Value"], new[] { new[] { "Captured", snapshot.GeneratedAt.ToString("O") }, - new[] { "Coverage", snapshot.IsPartial ? "Partial" : "Complete for listed sources" }, + new[] { "Coverage", intake.Coverage }, new[] { "Inventory entries", snapshot.Applications.Count().ToString() }, new[] { "Entries without reported sizes", snapshot.Applications.Count(a => !a.ReportedSizeBytes.HasValue).ToString() }, new[] { "First inventory results", snapshot.FirstResultsDuration is { } first ? $"{first.TotalSeconds:N3}s" : "Unknown" }, @@ -117,6 +135,32 @@ private static IEnumerable
Sections(InventorySnapshot snapshot) private string Protect(string value, InventorySnapshot snapshot, bool raw) => raw ? value : _redaction.Redact(value, snapshot.Machine); - private static string Cell(string value) => Formatting.MarkdownEscape(value.Replace("\r", " ").Replace("\n", " ")).Replace("<", "<").Replace(">", ">"); + private void RedactValues(JsonNode node, InventorySnapshot snapshot) + { + // Redact values before encoding: replacing serialized text can rename JSON + // properties or miss identifiers that contain HTML/JSON escape characters. + if (node is JsonObject obj) + { + foreach (var (key, child) in obj.ToArray()) + if (child is JsonValue value && value.TryGetValue(out var text)) obj[key] = Protect(text, snapshot, false); + else if (child is not null) RedactValues(child, snapshot); + } + else if (node is JsonArray array) + { + for (var i = 0; i < array.Count; i++) + if (array[i] is JsonValue value && value.TryGetValue(out var text)) array[i] = Protect(text, snapshot, false); + else if (array[i] is { } child) RedactValues(child, snapshot); + } + } + private static string Cell(string value) + { + var text = value.Replace("\r", " ").Replace("\n", " ").Replace("&", "&") + .Replace("<", "<").Replace(">", ">"); + // Captured strings and client notes are data, including Markdown links/images. + foreach (var character in new[] { "\\", "`", "*", "_", "[", "]", "|" }) + text = text.Replace(character, "\\" + character); + return text; + } private static string Percent(double? value) => value.HasValue ? $"{value:N1}%" : "Unknown"; + private static string Response(string value) => string.IsNullOrWhiteSpace(value) ? "Not provided" : value.Trim(); } diff --git a/src/AppLens.Backend/WorkflowIntake.cs b/src/AppLens.Backend/WorkflowIntake.cs new file mode 100644 index 0000000..5fe2168 --- /dev/null +++ b/src/AppLens.Backend/WorkflowIntake.cs @@ -0,0 +1,53 @@ +namespace AppLens.Backend; + +/// Optional client statements, not facts inferred by the scanner. +public sealed record WorkflowClientContext +{ + public string Goal { get; init; } = ""; + public string Services { get; init; } = ""; + public string Constraints { get; init; } = ""; +} + +public sealed record IntakeFact(string Label, string Value); + +/// A planning brief derived from the same capture used by the app table. +public sealed class WorkflowIntakeSummary +{ + public string Purpose { get; init; } = "Technical intake for designing a workflow that fits this PC."; + public string Coverage { get; init; } = "Unknown — no source results were recorded."; + public List Facts { get; init; } = []; + public List PlanningLimits { get; init; } = + [ + "Installed software does not confirm a working installation, sign-in, license, or access to a service.", + "Hardware and available memory/storage are observations at capture time, not a workload benchmark or a compatibility guarantee.", + "Network connectivity between devices, cloud resources, account permissions, GPU memory, and firmware update availability are not assessed by this scan." + ]; + + public static WorkflowIntakeSummary From(InventorySnapshot snapshot) + { + var machine = snapshot.Machine; + return new() + { + Coverage = snapshot.ProbeStatuses.Count == 0 ? "Unknown — no source results were recorded." : + snapshot.IsPartial ? "Partial — see Scan coverage for missing or failed readings." : "Complete for listed sources only.", + Facts = + [ + new("Device", Known(string.Join(" ", new[] { machine.Manufacturer, machine.Model }.Where(s => !string.IsNullOrWhiteSpace(s))))), + new("Operating system", Known(machine.OSDescription)), + new("Architecture", Known(machine.OSArchitecture)), + new("CPU", Join(machine.Processors)), + new("GPU", Join(machine.Graphics)), + new("RAM total", InventoryFormatting.Size(machine.TotalMemoryBytes)), + new("RAM available at capture", InventoryFormatting.Size(machine.FreeMemoryBytes)), + new("Storage", machine.Disks.Count == 0 ? "Unknown" : string.Join("; ", machine.Disks.Select(d => + $"{Known(d.Volume)}: {InventoryFormatting.Size(d.FreeBytes)} free of {InventoryFormatting.Size(d.TotalBytes)}"))), + new("Application entries observed", snapshot.Applications.Count(a => a.Kind is ApplicationKind.Desktop or ApplicationKind.Store).ToString()), + new("Runtime entries observed", snapshot.Applications.Count(a => a.Kind == ApplicationKind.Runtime).ToString()) + ] + }; + } + + public string ToDisplayText() => string.Join("\n", Facts.Select(f => $"{f.Label}: {f.Value}")); + private static string Join(IEnumerable values) => Known(string.Join("; ", values.Where(v => !string.IsNullOrWhiteSpace(v)))); + private static string Known(string? value) => string.IsNullOrWhiteSpace(value) ? "Unknown" : value.Trim(); +} diff --git a/src/AppLens.Desktop/MainWindow.xaml b/src/AppLens.Desktop/MainWindow.xaml index 138d420..aa5a4a2 100644 --- a/src/AppLens.Desktop/MainWindow.xaml +++ b/src/AppLens.Desktop/MainWindow.xaml @@ -9,14 +9,16 @@ - + +