{"id":28684981,"url":"https://github.com/markbattistella/formlogger","last_synced_at":"2026-04-13T12:01:23.434Z","repository":{"id":297207393,"uuid":"995315646","full_name":"markbattistella/FormLogger","owner":"markbattistella","description":"FormLogger is a drop-in SwiftUI-compatible manager for logging bugs, feature requests, and feedback - with flexible support for custom UI and backends. You can roll your own interface while leveraging the powerful FormManager. It’s designed to integrate with lightweight backends (like a Cloudflare Worker) that push issues to GitHub via their API.","archived":false,"fork":false,"pushed_at":"2026-03-08T11:23:00.000Z","size":94,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-08T14:45:33.562Z","etag":null,"topics":["automation","github-issues","ios","ipados","macos","swift","swiftui","tvos","visionos","watchos"],"latest_commit_sha":null,"homepage":"https://swiftpackageindex.com/markbattistella/FormLogger/documentation","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/markbattistella.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["markbattistella"],"custom":["https://www.paypal.me/markbattistella/5AUD","https://www.paypal.me/markbattistella/10AUD","https://www.paypal.me/markbattistella/20AUD"]}},"created_at":"2025-06-03T09:45:38.000Z","updated_at":"2026-03-08T11:22:39.000Z","dependencies_parsed_at":null,"dependency_job_id":"442f181d-42e6-414f-8816-2fcc96040f6e","html_url":"https://github.com/markbattistella/FormLogger","commit_stats":null,"previous_names":["markbattistella/formlogger"],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/markbattistella/FormLogger","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markbattistella%2FFormLogger","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markbattistella%2FFormLogger/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markbattistella%2FFormLogger/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markbattistella%2FFormLogger/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/markbattistella","download_url":"https://codeload.github.com/markbattistella/FormLogger/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markbattistella%2FFormLogger/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31751705,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-13T09:16:15.125Z","status":"ssl_error","status_checked_at":"2026-04-13T09:16:05.023Z","response_time":93,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","github-issues","ios","ipados","macos","swift","swiftui","tvos","visionos","watchos"],"created_at":"2025-06-14T04:00:17.473Z","updated_at":"2026-04-13T12:01:23.428Z","avatar_url":"https://github.com/markbattistella.png","language":"Swift","funding_links":["https://github.com/sponsors/markbattistella","https://www.paypal.me/markbattistella/5AUD","https://www.paypal.me/markbattistella/10AUD","https://www.paypal.me/markbattistella/20AUD"],"categories":[],"sub_categories":[],"readme":"\u003c!-- markdownlint-disable MD033 MD041 --\u003e\n\u003cdiv align=\"center\"\u003e\n\n# FormLogger\n\n![Swift Versions](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fmarkbattistella%2FFormLogger%2Fbadge%3Ftype%3Dswift-versions)\n\n![Platforms](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fmarkbattistella%2FFormLogger%2Fbadge%3Ftype%3Dplatforms)\n\n![Licence](https://img.shields.io/badge/Licence-MIT-white?labelColor=blue\u0026style=flat)\n\n\u003c/div\u003e\n\n`FormLogger` is a drop-in SwiftUI-compatible manager for logging bugs, feature requests, and feedback - with flexible support for custom UI and backends. You can roll your own interface while leveraging the powerful `FormManager`. It's designed to integrate with lightweight backends (like a Cloudflare Worker) that push issues to GitHub via their API.\n\n## Features\n\n- **Input validation:** Ensures title, description, and (optional) contact details are properly filled and formatted.\n- **Customisable view model:** Use the underlying `FormManager` to power your own UI with full control over behaviour.\n- **Repository routing:** Supports single, multiple, or selectively overridden repositories based on form type.\n- **Log attachment:** Automatically collects and submits log data alongside user input for better context.\n- **Async submission:** Handles network requests using `async`/`await`, with detailed progress and error state handling.\n\n## Installation\n\nAdd `FormLogger` to your Swift project using Swift Package Manager.\n\n```swift\ndependencies: [\n  .package(url: \"https://github.com/markbattistella/FormLogger\", from: \"1.0.0\")\n]\n```\n\nAlternatively, you can add `FormLogger` using Xcode by navigating to `File \u003e Add Packages` and entering the package repository URL.\n\n## Usage\n\nWith full control you can leverage the `FormManager` to power your own interface:\n\n### Capabilities\n\n- `userInput`: Contains title, description, and optional contact info.\n- `formType`: Enum of `.bug`, `.feature`, or `.feedback`.\n- `isFormValid`: Boolean indicating if user input is valid.\n- `isProcessing`: Boolean showing if submission is ongoing.\n- `submit()`: Async method that validates, collects logs, and sends to backend.\n- `currentProgress`: Double value from 0–1 for progress.\n- `currentProgressLabel`: String description of current progress state.\n\n## Configuration\n\nYou can customise how form submissions are routed to repositories using the `RepositoryResolver`.\n\nThis modular setup ensures you can scale your feedback system as your project grows - from a single inbox to a fully segmented triage workflow.\n\nThere are three main strategies depending on your needs:\n\n### Single Repository\n\nUse a single repository for all form types - bugs, features, and feedback. This is a great setup if you're just starting out or want to centralise everything in one place.\n\n```swift\nlet singleRepoConfig = PreviewFormConfig(\n  repository: .single(\n    Repository(\n      username: \"markbattistella\",\n      repository: \"feedback-logger\"\n    )\n  )\n)\n```\n\nIn this setup, all issues - regardless of type - are submitted to the same repository. It's simple, clean, and requires minimal setup.\n\n### Multiple Repositories\n\nUse different repositories depending on the type of form being submitted. Ideal when you want to separate concerns, visibility, or contributor access.\n\n```swift\nlet multiRepoConfig = PreviewFormConfig(\n  repository: .multiple([\n    .bug: Repository(\n      username: \"markbattistella\",\n      repository: \"bug-tracker\"\n    ),\n    .feature: Repository(\n      username: \"markbattistella\",\n      repository: \"feature-requests\"\n    ),\n    .feedback: Repository(\n      username: \"markbattistella\",\n      repository: \"feedback-logger\"\n    )\n  ])\n)\n```\n\nThis gives you fine-grained control:\n\n- Bugs could be logged to a private internal repo.\n- Feedback might go to a public or community-accessible submodule.\n- Feature requests could be tracked openly so others can view and upvote them.\n\n### Partial Override\n\nStart with a shared repository for all form types, but selectively override one or two categories. Great for workflows where most issues can be public, but certain categories (like bugs) need privacy.\n\n```swift\nlet overrideRepoConfig = PreviewFormConfig(\n  repository: .partial(\n    shared: Repository(\n      username: \"markbattistella\",\n      repository: \"feedback-logger\"\n    ),\n    overrides: [\n      .bug: Repository(\n        username: \"markbattistella\",\n        repository: \"bugs-internal\"\n      )\n    ]\n  )\n)\n```\n\nPerfect when:\n\n- Feedback and feature requests are sent to a public-facing repo.\n- Bug reports go to a private, locked-down internal repo accessible only to your team.\n\n## Validation\n\nBefore submission, `FormManager` checks the user's input for completeness and correctness. If validation fails, it throws a `FormValidationError`.\n\n```swift\npublic struct FormValidationError: Error {\n  public let invalidFields: Set\u003cFormField\u003e\n}\n```\n\nEach invalid field is represented by a `FormField` enum, making it easy to highlight or handle specific errors in your UI.\n\nValidation covers:\n\n- **Title:** Cannot be empty or just whitespace.\n- **Description:** Required, trimmed, and validated.\n- **Contact Info:** Optional, but if enabled, both name and email must be valid.\n  - Email format is checked using a *very* basic regex pattern.\n\nYou can access validation state live via:\n\n```swift\nviewModel.isFormValid // Bool\n```\n\nOr if you wish to get the invalid fields for display you can access `fieldErrors`:\n\n```swift\n// example\nif let error = viewModel.fieldErrors[.title] {\n  Text(error)\n    .font(.caption)\n    .foregroundColor(.red)\n}\n```\n\n### HTTP Response Handling\n\nWhen a form is submitted, the backend response is captured as a `FormResponse`, which conforms to `Error`.\n\n```swift\npublic enum FormResponse: Error {\n  case badRequest         // 400\n  case unauthorized       // 401\n  case serverError        // 500\n  case unexpectedError    // any other failure\n  case successMessage     // 200–299 success\n}\n```\n\n## Backend System\n\nWhile `FormLogger` is backend-agnostic, it's built to work beautifully with lightweight systems.\n\n\u003e [!CAUTION]\n\u003e The log file sent in the multipart form is GZIP-compressed and saved with a `.gz` extension. It must be decompressed on the backend using a GZIP-compatible decompression method.\n\n### What I used\n\nI've set mine up using:\n\n- A GitHub App for API authentication\n- A Cloudflare Worker to receive and forward form data\n- Logs and metadata are submitted as a multipart form\n\n\u003e [!TIP]\n\u003e I wrote about setting this up for yourself - [part 1](https://markbattistella.com/writings/2025/rethinking-feedback-p1/), [part 2](https://markbattistella.com/writings/2025/rethinking-feedback-p2/), [part 3](https://markbattistella.com/writings/2025/rethinking-feedback-p3/), and [part 4](https://markbattistella.com/writings/2025/rethinking-feedback-p4/)\n\nThis lets me forward validated SwiftUI form data directly to GitHub as an issue — but you can use any backend that accepts JSON and logs.\n\n## Contributing\n\nContributions are welcome! Please fork the repository and submit a pull request for any features, fixes, or improvements.\n\n## License\n\n`FormLogger` is available under the MIT license. See the LICENCE file for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkbattistella%2Fformlogger","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarkbattistella%2Fformlogger","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkbattistella%2Fformlogger/lists"}