<feed xmlns="http://www.w3.org/2005/Atom"> <id>https://msicc.net/</id><title>MSicc&#39;s Blog</title><subtitle>MSicc&#39;s Blog | from my life as a developer (and more)</subtitle> <updated>2026-07-21T10:09:48+02:00</updated> <author> <name>Marco Siccardi</name> <uri>https://msicc.net/</uri> </author><link rel="self" type="application/atom+xml" href="https://msicc.net/feed/"/><link rel="alternate" type="text/html" hreflang="en" href="https://msicc.net/"/> <generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator> <rights> © 2026 Marco Siccardi </rights> <icon>/assets/img/favicons/favicon.ico</icon> <logo>/assets/img/favicons/favicon-96x96.png</logo> <entry><title>AI Context Kit: Cross-session persistence, context compression, easier distribution</title><link href="https://msicc.net/ai-context-kit-cross-session-persistence-context-compression-easier-distribution/" rel="alternate" type="text/html" title="AI Context Kit: Cross-session persistence, context compression, easier distribution" /><published>2026-05-15T06:00:00+02:00</published> <updated>2026-07-21T06:35:32+02:00</updated> <id>https://msicc.net/ai-context-kit-cross-session-persistence-context-compression-easier-distribution/</id> <author> <name>msicc</name> </author> <category term="Dev Stories" /> <category term="Tools" /> <category term="AI &amp;&amp; LLMs" /> <summary>AI Context Kit gets cross-session persistence via checkpoint artifacts, a user-confirmed context compression workflow, and simplified distribution for Claude Code, GitHub Copilot CLI, and OpenAI Codex.</summary> <content type="text">Since the last update to AI Context Kit, I have been testing it in various real-world scenarios. One thing that frustrates me (and a lot of other developers) with AI-assisted development is that every session starts from zero. You load your user context, explain the project, re-establish the role, remind the assistant where you left off — and that is before you even type the first real request. This update brings three additions to AI Context Kit v1.4.2 to address this and more: cross-session persistence via checkpoint artifacts (section 4.4), a user-confirmed context compression workflow (section 4.5), and simplified distribution for Claude Code, GitHub Copilot CLI, and OpenAI Codex. Each is covered below. Cross-Session Persistence (new spec section 4.4) The new section 4.4 introduces checkpoint artifacts, a structured YAML file that captures session state so it can be restored in a future conversation. By explicitly defining the format and rules for these checkpoint files, we can ensure they can be used correctly between sessions, users, and even different assistants. Here is a sample checkpoint file: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 --- checkpoint: true project: &quot;my-app&quot; role: &quot;Developer&quot; phase: &quot;Implementation&quot; output_style: &quot;concise&quot; tone: &quot;technical&quot; interaction_mode: &quot;pair&quot; open_tasks: - &quot;Complete order validation logic&quot; - &quot;Write integration tests for /api/orders&quot; key_decisions: - &quot;Use soft delete over hard delete for order records&quot; - &quot;Paginate results with cursor-based pagination for scalability&quot; active_files: - &quot;Services/OrderService.cs&quot; - &quot;Controllers/OrdersController.cs&quot; last_updated: &quot;2026-05-12T18:00:00Z&quot; --- Checkpoint files must be stored outside the instruction layer — they must not modify AGENTS.md or your user context files. The checkpoint creation skill will suggest an appropriate location during the creation workflow. The checkpoint: true marker is what makes the file self-identifying — the restore workflow checks for it first, so a regular YAML file cannot be accidentally applied as session state. The restore rules are equally explicit: checkpoint state is only applied at session start, conflicts with active instruction files are surfaced one by one for the user to resolve, instruction files win by default if you do not respond, and nothing proceeds until you confirm the full restored state. No silent overwrites. The assistant will propose creating a checkpoint when you signal you are wrapping up — but it will never write one without your explicit approval. You can have a look at the full spec for this feature in the new section 4.4 of the AI Context Kit specification. The Operational Skills: create-checkpoint and restore-checkpoint The spec sections alone are nice to have, but they do not make behavior automatic. This is why I also introduced two skills to make this new workflow actionable: create-checkpoint walks your AI assistant through collecting all required schema fields from the active session, drafts the artifact, presents it for your review, and writes it only after you explicitly approve. Sensitive data — credentials, private client content — is explicitly excluded. restore-checkpoint reads the artifact, validates the checkpoint: true marker and required fields, compares each value against your active instruction files, surfaces any conflicts for resolution, and presents the full resolved state before proceeding. Both skills are available as prompt files for one-line invocation, and they should surface automatically in your assistant’s skill picker. Context Compression (new spec section 4.5) Long sessions run into context window limits. With the introduction of this new section, compression is no longer a provider-side event that happens without warning and without negotiation. We are making compression a user-confirmed workflow. When context saturation is evident, the assistant must describe what will be retained and what will be dropped — before asking for confirmation. It must also offer to export the current uncompressed state to a checkpoint file first, so you have a fallback restore point. After compression, the assistant must not imply that dropped context is recoverable. What is gone is gone, and the spec requires honesty about that. In practice, the interaction looks something like this: Assistant: Context saturation is approaching. Before compressing, I recommend exporting a checkpoint — this gives you a restore point if you need to recover dropped context later. Here is what will be retained: active files, open tasks, and the last three decisions. The following will be dropped: earlier code review comments and the initial scaffolding discussion. Do you want to export a checkpoint first, or proceed directly with compression? You can read the full details of this workflow in the new section 4.5 of the AI Context Kit specification. Simplifying Distribution of the kit The process of setting up AI Context Kit was always something I wanted to be as simple as possible, but the reality is that it still required a bit of manual work to clone the repo, copy files, and set up your assistant’s configuration. In the meantime, some better standards have emerged for distributing AI tools and skills, and I wanted to align with those to make it easier for developers to adopt the kit. Claude Code Plugin AI Context Kit ships a .claude-plugin/plugin.json manifest that makes the repository installable as a native Claude Code plugin. Once installed, all skills are registered as namespaced slash commands — no manual SKILL.md loading required. 1 2 3 4 5 6 7 8 # Register the marketplace (one-time) claude plugin marketplace add MSiccDev/ai-context-kit # Install claude plugin install ai-context-kit@ai-context-kit # Project-scoped install (shared via .claude/settings.json) claude plugin install ai-context-kit@ai-context-kit --scope project Inside a session, skills are invoked with their plugin namespace: 1 2 /ai-context-kit:create-usercontext-instructions /ai-context-kit:create-checkpoint GitHub Copilot CLI Plugin Claude Code and GitHub Copilot CLI share the same plugin spec. The same .claude-plugin/ directory also contains a marketplace.json that makes the repo a self-hosted marketplace. The install commands are identical: 1 2 copilot plugin marketplace add MSiccDev/ai-context-kit copilot plugin install ai-context-kit@ai-context-kit OpenAI Codex Support Codex uses a different discovery mechanism: it scans upward from the working directory for a .agents/skills/ folder. AI Context Kit now ships that directory with symlinks pointing to the canonical skills/ folders as needed for Codex discovery. Each skill also has an agents/openai.yaml sidecar with the display name, short description, and default prompt that the Codex skill picker uses. If you install the kit, all skills should be discovered automatically. Please note that these distribution methods are not mutually exclusive. You can choose to install the kit as a plugin in Claude Code, and at the same time have it available as a local skill set for OpenAI Codex. My goal with this update was to make all skills and features accessible with minimal setup, regardless of your preferred AI assistant. Conclusion With the introduction of cross-session persistence and context compression, AI Context Kit now has another layer of control and reliability. By combining the persistence with the compression feature, you can now have long-running sessions that can be stretched far longer than before without losing control over the context or risking silent data loss. By simplifying the distribution and installation process, I hope to make it easier for developers to adopt the kit and integrate it into their workflows, regardless of their preferred AI assistant. Have you tried my Context Kit already? What do you think about these new features? Do you have any suggestions for improvement or new features you would like to see? Let me know in the comments below or by opening an issue in the repository. As always, I hope this post and the kit itself are useful for some of you. Until the next post, happy coding, everyone! Title image note: The title image of this post was generated with the help of AI. It visually represents AI Context Kit as structured infrastructure — a stable, secure instruction layer at the center of complex, multi‑model AI workflows. Disclaimer: This blog post was written with the help of AI. I provided the structure and key points, reviewed the draft, and edited it to fit my style and technical standards.</content> </entry> <entry><title>Skills Central: A Pragmatic Setup for Reusable AI Skills</title><link href="https://msicc.net/2026-03-19-skills-central-a-pragmatic-setup-for-reusable-ai-skills/" rel="alternate" type="text/html" title="Skills Central: A Pragmatic Setup for Reusable AI Skills" /><published>2026-03-19T17:00:00+01:00</published> <updated>2026-07-21T06:35:32+02:00</updated> <id>https://msicc.net/2026-03-19-skills-central-a-pragmatic-setup-for-reusable-ai-skills/</id> <author> <name>msicc</name> </author> <category term="Dev Stories" /> <category term="Tools" /> <category term="AI &amp;&amp; LLMs" /> <summary>Skills are becoming the go-to solution for reusable AI workflows, but there’s still a lot of ambiguity around how to structure them effectively. In this post, I share my pragmatic setup for skills that balances modularity, maintainability, and ease of use in a day-to-day development context.</summary> <content type="text">If you are following my work, you know that I try to incorporate reusable AI workflows into my projects as much as possible. I’ve been using skills for a while now and in this post, I want to share my pragmatic setup for skills that balances modularity, maintainability, and ease of use in a day-to-day development context. How the Skills Central repository came to be During the development of one of my side projects, I found myself referencing the same set of skills that I used already in another project. I realized that I was constantly symlinking the same skills from multiple external repo forks that I cloned locally. I can foresee a future where I have dozens of forked repos that I have to manage and keep in sync with their original source. A maintenance nightmare. I took a step back and thought about how I could solve this problem. After a short walk and some brainstorming, I came up with the idea of “Skills Central” - a single repository that contains all of the skills that I (want to) use across my projects, organized in a way that makes it easy to find and reuse them. After a quick chat with ChatGPT to discuss the idea and refine some details, I asked it to generate a prompt for Codex to create the initial structure of the repository. You can find the prompt I used and refined with Codex here . Please note that the prompt is quite detailed and specific to my needs, but it can be adapted to fit your own requirements. The structure of Skills Central Codex took my prompt and generated the initial structure of my Skills Central repository. Here is how it organized it: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 skills-central/ ├── .gitignore ├── README.md ├── CONTRIBUTING.md ├── docs/ │ ├── architecture.md │ ├── imports.md │ └── usage.md ├── scripts/ │ └── bootstrap-skills.sh ├── templates/ │ └── skill-source-template.md └── skills/ ├── stable/ │ ├── shared/ │ │ ├── architecture/ │ │ ├── review/ │ │ ├── tdd/ │ │ └── writing/ │ ├── ai/ │ ├── dotnet/ │ ├── maui/ │ └── swift/ └── experimental/ ├── shared/ │ ├── architecture/ │ ├── review/ │ ├── tdd/ │ └── writing/ ├── ai/ ├── dotnet/ ├── maui/ └── swift/ Here are the key components of the structure: skills-central/: The root directory of the repository. scripts/: A directory containing scripts to automate tasks, such as the bootstrap script for symlinking skills. (spoiler - this one will not survive until the end) docs/: A directory containing documentation on the architecture, how to import skills, and how to use them effectively. templates/: A directory containing templates for creating new skill sources, ensuring consistency across the repository. skills/: The main directory where all the skills are stored, organized into stable and experimental categories, and further categorized by domain (e.g., AI, .NET, MAUI, Swift) and shared skills (e.g., architecture, review, TDD, writing). First Skills: repository management With this structure in place, I was ready to start populating the repository with skills. I quickly realized, though, that Codex’s own import skills were only available in Codex itself. My goal, however, is to be provider agnostic. I confronted Codex with this problem - and so it came to be that we created a set of skills for importing skills from GitHub repositories and installing them into Skills Central. We also removed the scripts/ folder as it was no longer needed afterwards. The first important skills were these two: github-skill-importer: A skill to import a foreign skill from a GitHub repository into the central repository, copying it into the correct functional category and generating a skill-source.md file. project-skill-installer: A skill to install selected shared skills from the central repository into a project’s root skills/ tree, supporting both symlink and copy modes. This skill made the scripts folder with the bootstrap script obsolete, as it provided a more structured and reusable way to manage skill installations across projects. After these two skills were in place, I was able to easily import skills from other repositories (for example some of the skills Paul Hudson references in his Swift skills repository) and install them into my projects without having to worry about managing multiple forks or symlinks. But I didn’t stop there. I also created a set of skills for authoring new reusable skills in a provider-agnostic way. On top of this core skill, I built provider-specific add-ons for OpenAI, Anthropic, and GitHub Copilot with the help of Codex. This way, I can ensure that my skill creation process is consistent and efficient no matter which provider I am working with (I tend to switch between them every now and then). If you want to see the full list of skills and their purposes, you can check out this prompt that will help you to create them for your own setup. Why not open source it? You might be wondering why I haven’t open-sourced my Skills Central repository. The truth is, I have considered it, but I have decided against it for now. There are a few reasons for this decision: The repo contains a lot of personal and project-specific skills that may not be relevant or useful to others. This is just my approach of structuring and managing skills, and it may not be the best fit for everyone. I want to keep it as a personal tool that I can evolve and adapt as my needs change. As we live in a fast-paced world of AI and LLMs, I want to keep the flexibility to experiment and iterate on the structure and content of the repository without worrying about breaking someone else’s setup or expectations. That being said, I am open to sharing specific skills or insights from my setup if there is interest. I also encourage others to take inspiration from my approach and adapt it to their own needs. The goal is to create a system that works for you and helps you to manage your reusable AI workflows effectively, whether that means using a similar structure or coming up with your own unique solution. With the help of the prompts I used to create my Skills Central repository, you can easily set up your own version of it and start managing your reusable AI workflows in a more organized and efficient way. How to maintain foreign Skills in the long term? One of the challenges of using skills from external sources is keeping them up to date and in sync with their original source. To address this, I have implemented the follwing strategy: For skills that I import from external repositories, I create a skill-source.md file that contains metadata about the skill, including its original source, the upstream path, imported commit, tag or version, and its import date. This way, I have a clear record of where the skill came from and when it was last updated. I give all imported skills a local status (one of mirror, adapted, or forked) that indicates how closely they track the original source. This helps me to prioritize which skills need to be reviewed and updated regularly. notes about local modifications and adaptations are also included in the skill-source.md file, so I can easily track any changes I make to the skill and how they differ from the original source. Skills with a mirror status will be reviewed for updates on a regular basis to check if there are any new commits or versions in the original source that need to be imported. For skills with an adapted or forked status, I will review them on a case-by-case basis, depending on how critical they are for my projects and how much they have diverged from the original source. I plan to extend the meta data in the skill-source.md file to include statistics about the skill’s usage across my projects, such as how many times it has been installed and used, and any feedback or issues that I have encountered with it. This will help me to prioritize which skills to maintain and update based on their importance and impact on my projects - and also to identify any skills that may need to be deprecated or removed if they are no longer relevant or useful. Conclusion Skills are powerful tools for reusable AI workflows, but managing them can be challenging. I created the Skills Central repo as a pragmatic solution to this problem in my own projects and in my environment. I hope that sharing my setup and the thought process behind it can inspire others to create their own systems for managing reusable AI workflows. Whether you choose to adopt a similar structure or come up with your own unique solution, the key is to find a system that works for you and helps you to manage your skills effectively. What is your approach to managing skills you keep using across projects? Did you choose another solution to solve this problem? I would love to hear about it in the comments or on my social media channels. As always, I hope this post is helpful for some of you! Until the next time, happy coding, everyone! Title image note: The title image of this post was generated with the help of AI. It illustrates the concept of a central repository for reusable AI skills, with a visual representation of interconnected nodes representing different skills and their relationships.</content> </entry> <entry><title>AI Context Kit, Evolved: Why I Moved to AGENTS.md + Agent Skills (and how I used the Codex macOS App for the Migration)</title><link href="https://msicc.net/2026-02-21-ai-context-kit-adapt-agents-md-and-agent-skills/" rel="alternate" type="text/html" title="AI Context Kit, Evolved: Why I Moved to AGENTS.md + Agent Skills (and how I used the Codex macOS App for the Migration)" /><published>2026-02-21T07:00:00+01:00</published> <updated>2026-07-21T06:35:32+02:00</updated> <id>https://msicc.net/2026-02-21-ai-context-kit-adapt-agents-md-and-agent-skills/</id> <author> <name>msicc</name> </author> <category term="Dev Stories" /> <category term="Tools" /> <category term="AI &amp;&amp; LLMs" /> <summary>After releasing AI Context Kit, I learned about two emerging standards that are becoming essential for durable AI collaboration: AGENTS.md for project operation and Agent Skills for workflow authority. In this post, I explain why I transitioned to these standards, how I executed the migration using the Codex macOS App, and what the new architecture looks like.</summary> <content type="text">When I released the first version of AI Context Kit in early January, I already had a solid foundation: specification, templates, and reusable prompt workflows. But once the project was “in the wild”, I started to learn more about two standards that are quickly becoming essential for durable AI collaboration: AGENTS.md as the operational entrypoint Agent Skills as the canonical workflow layer This post is about that transition, why I made it, and how I executed it test-driving the new Codex macOS App as a real-world test. What I learned after v1.2 The first release solved a real pain point: context drift across tools and projects. But it also revealed some architectural weaknesses that I needed to address to support my long-term vision of provider-agnostic AI collaboration. 1. AGENTS.md is already the de facto standard for project operation My first approach to use project-instructions files as the operational entrypoint worked and is still valid. However, I quickly realized that AGENTS.md is already widely adopted as the standard for defining project-level operational rules and information. So I decided to align with that standard to make it easier for users to adopt AI Context Kit without needing to learn a custom structure. 2. Skills should own the workflow logic, not prompt files In v1.2, prompts still carried too much execution logic. It worked great over all, but the prompts where really big and contained a lot of logic combined, which made them less reusable and more prone to drift. By moving the workflow logic into dedicated Skill definitions, I can make the prompts much thinner and more focused on just being composition wrappers that defer to the Skill for the actual logic. This also makes it easier to evolve the workflow logic without needing to rewrite those big prompt files. The current structure of AI Context Kit is now much more modular and aligned with these standards, which should make it easier to maintain and evolve over time. Using Codex for macOS Around the same time I learned about these standards, OpenAI released the Codex macOS App, which is designed to help developers execute code changes with AI assistance. I thought this would be a perfect opportunity to test-drive the app in a real-world scenario by using it to execute the migration of AI Context Kit to the new architecture. The process was intentionally strict: Plan the transition in explicit phases. Execute changes step by step. Keep human review gates between meaningful changes. Validate after each major refactor. Run multiple cleanup passes before release readiness. This gave me two outcomes at once: The repository was successfully migrated to the new architecture, with clear separation of concerns and alignment with standards. I got a practical test of Codex in a real documentation-governance migration, not just a toy coding task. My key takeaway: most workflows work best when AI is used as a disciplined execution partner, while architectural decisions and approval gates stay human-led. I found that Codex was great at executing well-defined tasks, but it still required careful chunking, prompting, and review to ensure the changes aligned with my vision and standards. What the repository looks like now After the migration, AI Context Kit is now centered around the two standards: AGENTS.md standard (project operation layer) Root AGENTS.md is the primary operational entrypoint. It defines precedence rules, session-state behavior, command namespace policy, and drift-control responsibilities. Project context moved away from legacy project-instructions artifacts into AGENTS-first guidance. Agent Skills standard (workflow authority layer) skills/ is now the canonical home for create/validate/governance workflows. Skill-local references/ hold detailed checklists and rubric logic. Composition wrappers in prompts/ are intentionally slim and defer to skills. Supporting layers Spec remains authoritative: specs/context_aware_ai_session_spec.md (v1.3.1). Templates remain canonical structures in templates/. User context examples and validation artifacts remain in usercontexts/. Path stability and update rules are explicit to reduce future drift. In short: the structure is now clearer, more auditable, and easier to evolve without re-inflating prompts. Why this matters for provider-agnostic workflows My goal has never been “tool loyalty.” My goal is durable collaboration patterns that survive tool changes. This transition helps with exactly that: operational rules are explicit and standardized (AGENTS.md) workflow logic is reusable and centralized (Skills) wrappers stay portable and thin (Prompts) updates become easier to reason about (drift-control) That is a much better fit for both my vision for AI Context Kit and conforms better to the trends towards modular, standards-based AI collaboration. Conclusion Getting my AI Context Kit out was just the first step. Even if I had other plans for the next phase, I considered the adoption of these standards to be a necessary evolution to ensure the project’s long-term viability and alignment with the broader ecosystem. I will go through the open issues that I created after releasing v1.2 and update them to reflect the new architecture and standards and remove any that are no longer relevant due to the changes. AI Context Kit is now less a prompt collection and more a governed collaboration system: explicit contracts, canonical workflows, and predictable behavior across environments. Exactly what I wanted to build from the start, but now with a much clearer path to maintainability and evolution. If you are building your own AI workflow layer and want it to stay maintainable beyond the first release, AI Context Kit will help you get there faster by providing a tested structure and reusable patterns that are already aligned with the standards that are emerging in the ecosystem. Until the next post, happy coding, everyone! Title image note: The title image of this post was generated with the help of AI. It visually represents AI Context Kit as structured infrastructure — a stable, secure instruction layer at the center of complex, multi‑model AI workflows. Disclaimer: This blog post was written with the help of AI. I provided the structure and key points, reviewed the draft, and edited it to fit my style and technical standards.</content> </entry> <entry><title>Completing the LeadEssentials Course with the Blue Belt</title><link href="https://msicc.net/2026-02-07-leadessentials-course-completion/" rel="alternate" type="text/html" title="Completing the LeadEssentials Course with the Blue Belt" /><published>2026-02-07T07:00:00+01:00</published> <updated>2026-07-21T06:35:32+02:00</updated> <id>https://msicc.net/2026-02-07-leadessentials-course-completion/</id> <author> <name>msicc</name> </author> <category term="Courses" /> <category term="LeadEssentials" /> <summary>With the Blue Belt, I’ve completed the LeadEssentials course. The final modules focused on navigation, modular composition, testing strategies, and Swift Concurrency, tying together many architectural concepts that directly impact how I want to design and evolve real-world Swift codebases in the future.</summary> <content type="text">I’m happy to announce that I’ve completed the LeadEssentials course and earned the Blue Belt. With this belt, I’ve completed all lectures of the course. Compared to the earlier belts, this one required completing two modules. These went particularly deep into areas that I see influencing how I design, structure, test, and evolve real-world Swift codebases. Below is a summary of what the last two modules covered. Sixth Module: Navigation and Advanced Patterns The sixth module shifted the focus toward large-scale application structure, with navigation and composition taking center stage. A major theme throughout was how to design a navigation layer in Swift that remains clean, testable, and modular — even as an application grows and features become more independent. Rather than relying on tightly coupled navigation flows or shared global state, the course explored ways to navigate between features without breaking modular boundaries or introducing common anti-patterns. Closely tied to navigation was the broader topic of app composition and dependency management. I revisited dependency injection from both object-oriented and functional perspectives and learned when each approach is most appropriate. The module also covered dependency rejection patterns, different dependency lifestyles such as singleton, transient, and scoped dependencies, and how these choices affect testability and long-term maintainability. A recurring theme was how to decouple feature modules without introducing duplication, supported by refactoring techniques that rely heavily on tests and compiler guarantees. Another strong focus was reusability. This included creating generic components, structuring reusable presentation logic, and designing shared UI modules in a way that preserves modularity. Practical UI-related topics such as programmatic UI construction, scaling fonts correctly with Dynamic Type, and localized date formatting helped ground these architectural concepts in real user-facing concerns. The migration to diffable data sources was also covered, highlighting how they improve correctness and reduce complexity in list-based UIs. Testing, performance, and observability were woven throughout the module. I learned how to write fast and reliable automated tests by controlling environmental factors like the current date, locale, calendar, and time zone. Snapshot testing was explored as a complementary technique to unit and integration tests, and the module emphasized choosing the right test type for each level of the system. Debugging memory leaks using Xcode’s Memory Graph Debugger, understanding when to use autorelease pools, and explicitly releasing autoreleased instances during tests rounded out the performance-focused topics. Finally, the module addressed infrastructure concerns such as logging, profiling, and monitoring. Best practices for structured logging in Swift were combined with strategies for monitoring debug and release builds cleanly. On the asynchronous side, the course explored eliminating callback-heavy code by composing async operations with Combine, managing threading through schedulers, and reducing boilerplate while keeping asynchronous flows explicit and testable. Seventh Module: Swift Concurrency The seventh and final module focused entirely on Swift Concurrency and safe migration strategies. It began with enabling complete concurrency checking and learning how to interpret and resolve the resulting warnings and errors. A strong emphasis was placed on understanding concurrency boundaries by distinguishing between Sendable, @MainActor, and nonisolated contexts, as well as identifying unsafe concurrency behavior that the compiler cannot detect automatically. Migration strategies played a central role throughout this module. I learned how to progressively move existing codebases from completion closures, DispatchQueues, or Combine to async/await while preserving existing behavior, cancellation semantics, and test reliability. This included using checked continuations to bridge legacy APIs, applying deprecation strategies to introduce async alternatives without breaking clients, and understanding why not every API should immediately become asynchronous. The module also went deep into practical async/await usage. Topics included identifying which parts of the codebase should run on the main actor, replacing DispatchQueue-based synchronization with safer alternatives such as mutexes, and preparing projects for Swift 6’s stricter concurrency guarantees. More advanced scenarios, like migrating Core Data schedulers to concurrency-aware APIs and handling actor isolation across protocol boundaries, helped clarify how these concepts apply in larger, real-world applications. Testing asynchronous code received particular attention. I learned how to write deterministic async tests by creating dedicated stubs for success and failure cases, implementing reusable and generic async loader spies, and controlling task completion and cancellation explicitly. The course also covered how to test concurrent workflows using async let, how to wait for tasks in a deterministic way using tools like Task.yield, and how to migrate integration and acceptance tests to async/await without sacrificing reliability. The module concluded by tying concurrency back into composition. This included async injection in the composition root, migrating Combine-based compositions to async/await, defining async scheduler protocols for safe concurrent operations, and managing threading concerns explicitly at architectural boundaries. Conclusion Following this advanced curriculum with only a basic working knowledge of the Swift language and its core concepts was a challenge at times. I often needed longer than expected to understand the code examples and to implement them in my own course codebase. My prior experience with C# and .NET definitely helped bridge some of the gaps, but I also had to spend time looking up Swift-specific concepts and syntax. Reaching the Blue Belt feels less like an endpoint and more like a solid foundation for future work in Swift and SwiftUI. The later modules especially helped connect many dots: navigation, composition, dependency management, testing, and concurrency. These concepts now feel like parts of a coherent system instead of isolated techniques. More importantly, the course provided practical strategies for evolving existing codebases safely — something that matters far more in real projects than greenfield perfection — especially in the enterprise world, where most of my work happens. My next steps will be less about collecting more belts (which the program does offer) and more about applying these patterns consistently in my own apps (primarily in my side projects) and refactors. Having this body of knowledge tied together in a structured way was absolutely worth the effort and I’m excited to see how it influences my work in the coming years. There are still some other modules like the ‘iOS Dev Tooling’ module that I have started already and which I will complete in the coming weeks. I also plan to watch all lectures at least once again (which, funnily enough, translates into the next level of the Blue Belt) and complete my notes as long as I have access to the course (which closes this November). Click to see the full iOS Lead Essentials curriculum Disclaimer: This blog post was written with the help of AI, based on a bulleted summary of learning topics provided as part of the Lead Essentials program. The structured list served as the foundation for turning the content into a more readable, narrative-style post that reflects my personal learning experience. I reviewed and edited the post before publishing to ensure it meets the quality standards of this blog.</content> </entry> <entry><title>AI Context Kit: A Portable Instruction System for Context-Aware AI Collaboration</title><link href="https://msicc.net/2026-01-05-announcing-ai-context-kit/" rel="alternate" type="text/html" title="AI Context Kit: A Portable Instruction System for Context-Aware AI Collaboration" /><published>2026-01-05T15:00:00+01:00</published> <updated>2026-07-21T06:35:32+02:00</updated> <id>https://msicc.net/2026-01-05-announcing-ai-context-kit/</id> <author> <name>msicc</name> </author> <category term="Dev Stories" /> <category term="Tools" /> <category term="AI &amp;&amp; LLMs" /> <summary>AI Context Kit is a template repo for building structured, portable AI collaboration across LLM providers—complete with spec, templates, and validation prompts.</summary> <content type="text">Today I’m releasing “AI Context Kit”, a template repository that aims to make AI collaboration repeatable, portable, and predictable across LLM providers. If you use more than one model (or more than one project), you already know the problem: context gets lost, instructions drift, and the same prompt yields wildly different results. I built AI Context Kit to fix that with a structured instruction layer that stays stable across tools and sessions. Background This project started as a simple experiment: extracting prompts I kept reusing across different AI providers. Very quickly I noticed the real problem wasn’t the prompts — it was the missing context. Each tool had its own format, I had multiple projects running in parallel, and I kept re‑explaining the same things. So the idea evolved from reusable prompts to a layered instruction system: a personal user context + project‑specific instructions, backed by a spec. That architecture is what eventually became AI Context Kit. What does it solve? Most AI workflows start as ad‑hoc prompts. That works for quick questions, but breaks down in real projects when: roles and phases change over time, output format needs to stay consistent, project rules must be enforced, and different models interpret the same request differently. AI Context Kit turns this into a formal, reusable system. What do I get with AI Context Kit? The repo gives you a spec + templates + prompts approach: A formal specification for session state, roles, phases, and command handling Canonical templates for user context and project instructions Prompt workflows to create and validate instruction files That means you can define your context once, reuse it everywhere, and keep it versioned like any other part of your workflow. Diving into the repository If you only read one file, start with the spec: specs/context_aware_ai_session_spec.md This is the authoritative model for session state, transitions, and command namespacing. Then look at the canonical templates: templates/usercontext_template.instructions.md templates/project_template.instructions.md And finally the prompt workflows: prompts/create-usercontext-instructions.prompt.md prompts/create-project-instructions.prompt.md prompts/validate-usercontext-instructions.prompt.md prompts/validate-project-instructions.prompt.md Command namespacing (important) One design choice I’m enforcing here is namespaced commands. It prevents collisions when you load multiple instruction sets. Example: /ack.context /ack.mode developer /ack.phase implementation The spec documents the generic pattern as /tag.command, and this project uses /ack.* as its namespace. Quick start This is a template repository. To get started: Click “Use this template” on GitHub and create a new repository. Set the repository to Private (recommended, since your user context will probably contain personal data!) In your new repo, use the creation prompts to generate your user context and project instructions. Validate both files with the included prompts. Load them into your AI tool of choice. For updating your repository when the template evolves, follow the instructions in README.md. That’s it. From there, the AI should remain more consistent across sessions and tools. Who is this for? This is for AI‑interested developers who want: consistent collaboration across projects, a reusable instruction system, and a clear way to manage AI session state. If you’re already using prompts and instructions but feel like you’re constantly re‑explaining context, this is for you. The repo You can find the AI Context Kit repository here on my GitHub account: AI Context Kit Repository If you try it, I’d love to get feedback! I am looking forward to hearing your experiences, especially from people running multi‑project or multi‑LLM workflows. Feel free to comment below, open an issue on the repo, or reach out to me on Mastodon, Bluesky, or LinkedIn. Outlook This is just the beginning. If you look at the repository, you’ll already see a few issues for future improvements. My plan is to keep evolving the spec and templates based on real‑world usage - that’s why your feedback is so important! Until the next post, happy coding, everyone! Title image note: The title image of this post was generated with the help of AI. It visually represents AI Context Kit as structured infrastructure — a stable, secure instruction layer at the center of complex, multi‑model AI workflows. Disclaimer: This blog post was written with the help of AI. I provided the structure and key points, reviewed the draft, and edited it to fit my style and technical standards.</content> </entry> <entry><title>Looking Back at 2025: Learning, Architecture, and the Year AI Became Practical</title><link href="https://msicc.net/2025-12-30-looking-back-at-2025/" rel="alternate" type="text/html" title="Looking Back at 2025: Learning, Architecture, and the Year AI Became Practical" /><published>2025-12-30T07:00:00+01:00</published> <updated>2026-07-21T06:35:32+02:00</updated> <id>https://msicc.net/2025-12-30-looking-back-at-2025/</id> <author> <name>msicc</name> </author> <category term="Editorials" /> <summary>A personal review of my 2025: from certifications and software architecture work to SwiftUI, side projects, and how AI tools became a practical part of my daily development workflow.</summary> <content type="text">As 2025 comes to an end, it is time for another annual review. This year has been a year full of learning and growth, both personally and professionally. There were several highlights and challenges that shaped my year. Learning and Development This year, I have expanded my skills in various areas: I obtained the B1 Italian language certification with a score of 90/100 (read more about it here) I took and passed the iSAQB Foundation Level exam in Software Architecture I worked through the LeadEssentials curriculum and reached all 4 white belt stripes (read more about them here: 1st stripe, 2nd stripe, 3rd stripe, 4th stripe). I am now on my way to complete the journey and earn the blue belt (and with it, the completion certificate). I had to balance my learning with work and personal commitments, but I managed to stay consistent and motivated throughout the year. The rise of AI ChatGPT and Codex It should be no surprise that my year was also shaped by the rise of AI tools. I continued using ChatGPT for various tasks, from writing code snippets to helping me generate content for my blog. I recently started to use the Codex extension in VS Code and the first results are promissing. I asked ChatGPT to summarize how I used it throughout the year: Throughout 2025, Marco mainly used me as a technical sparring partner. We worked through architectural decisions, implementation approaches, debugging sessions, and trade-offs across .NET MAUI, SwiftUI, Entra ID, CI/CD pipelines, and prompt design. I was also part of his learning process — not to provide quick answers, but to explore concepts in depth, verify understanding, and challenge assumptions, especially in the context of software architecture and SwiftUI. On the writing side, I helped structure and refine technical blog posts, while the final wording, opinions, and responsibility always remained his. Overall, my role was not to replace thinking or decision-making, but to support it — by asking questions, offering alternatives, and helping document decisions more clearly. Github Copilot Github Copilot has become a regular part of my development workflow both at work and in side projects. One area I focused on was learning how to better prompt and instruct Copilot to generate more accurate and useful code. As most developers, I had to learn how to work with the tool effectively, understanding its strengths and limitations. AI Context Kit From these experiences I also started a new Open Source project called AI Context Kit. This project aims to make it easier for developers to create and share context between AI tools. I am testing it for myself at the moment and plan to release it officially in early 2026. If you are interested, you can follow the progress on GitHub . Claude (Code) As I continued to explore AI tools, I tried to use Claude and Claude Code to see if it could replace ChatGPT and Github Copilot. I started my AI Context Kit project with Claude and Claude Code and went quite far with it. Then, I tried to connect Claude Code in the browser to my GitHub account as they offered a 250$ credit to test it out that did not count towards the normal usage. I had some problems connecting it to GitHub and had to reconnect it a few times. I got it working at the end and went away from my computer for sleep. In the morning, I found out that the AI controlled review system had banned my account for suspicious activity. I had to appeal and explain the situation to get my account unbanned. As of writing this, I have not yet received a response from Anthropic regarding the issue. I did some research and found out that other users had similar problems. They needed to wait for weeks and even months to get their accounts unbanned, which is not acceptable for a developer relying on the tool for work. So I went back to using ChatGPT and Github Copilot. At work This year was full of new challenges and opportunities at work. Here is a brief overview: I continued to work on our .NET MAUI applications, implementing new features and improving performance. I did more backend work with ASP.NET Core and will continue to do so in 2026. I took responsibility for the EntraID integration in our mobile applications. I will expand this to the backend implementation in early 2026. As I obtained the iSAQB Foundation Level certification, I started to work on creating a software architecture framework with our lead architect. The goal is to standardize our architecture practices and improve the quality of our software solutions on multiple levels. We will continue this work in 2026. I joined the company’s internal AI guild, where we try to explore and implement AI tools and solutions in our projects. Side projects In addition to my work projects, I also dedicated time to my side projects: I shared some of my learnings on automating iOS releases with Github Actions this year in multiple blog posts (find them here). As I evolved through the LeadEssentials program, I started planning to move my TwistReader project away from .NET MAUI to Swift and SwiftUI. I plan to start this migration in early 2026. I started exploring the ChatGPT API and how to use it for creating a family meal planner application. I had to pause this project due to time constraints, but I learned a lot about working with the API and prompt engineering in general that I can apply to future projects. I continued to maintain TimeTraverseHub and made some improvements to the user experience and performance. I will soon reupload the latest version to TestFlight for beta testing. Besides the AI Context Kit project mentioned earlier, I also started another AI-related open source project called Awesome arc42 Copilot. This project aims to provide a collection of prompts and templates for using AI tools to support software architecture documentation based on the arc42 template. This project is still in its early stages, and I am currently testing it for myself in certain architecture documentation tasks (both at work and in my side projects). Personal Life Health and Fitness This year had also some personal ups and downs. It started with a big dental surgery in the beginning of 2025, which required some recovery time and patience. I eventually recovered well - only to face another challenge at the beginning of April when I had a gall bladder removal surgery. Again, the recovery was tough, but I managed to get through it. The latter one was especially hard for me as I was well prepared to run a half marathon in early April, which I had to cancel last minute due to the surgery. In early December, I had to undergo a minor surgery on my right foot. This time, the recovery was much easier and I was back on my feet in no time. Despite these health challenges, I tried my best to stay active and maintain my fitness routine. I kept running regularly and even participated in a few local races. I also tried to maintain a wholesome training routine, using strength training and flexibility exercises to complement my running. Next year, I plan to participate in a total of four half marathons - two in the spring (Zurich Half Marathon and GP Winterthur Half Marathon) and two in the fall (Greifenseelauf and Wiz Rome Half Marathon). I am already looking forward to these events and will start training for them in early 2026. In between, I will do some local shorter races as B-races. Family Family time remained a top priority for me this year. I made sure to spend quality time with my loved ones - regular family dinners, travels and other special occasions. These moments provided me with joy and support throughout the year. After more than two years since our first cat Amadeus passed away, we finally decided to get a new feline family member. In November, we started to go to a local animal shelter to find a new cat. After a few visits, we found Elina and Miro (siblings), who stole our hearts and adopted us right from the start. We took both of them home to us just yesterday. They are already settling in well and bringing a lot of joy and liveliness to our home. Looking Ahead to 2026 As I look ahead to 2026, I am excited about the opportunities and challenges that will come my way. As I mentioned earlier, I plan to continue my learning journey with the LeadEssentials program and aim to earn the blue belt. AI tools will continue to play a big role in my work and personal projects. At work, I will continue to focus on architecture work, AI integrations and further improving our internal applications. On the personal side, I will continue to prioritize health, fitness, and family time. Overall, 2025 has been a big year of growth for me. I am grateful for the experiences, lessons learned and challenges I mastered. I hope you had a great year as well and wish you all the best for the upcoming year! Until the next post, happy coding, everyone! The title image is generated with AI (ChatGPT) based on my prompt and the content of this post.</content> </entry> <entry><title>Lead Essentials: Main Composition Module finished (White Belt 4th stripe)</title><link href="https://msicc.net/2025-12-10-leadessentials-main-composition-module-finished/" rel="alternate" type="text/html" title="Lead Essentials: Main Composition Module finished (White Belt 4th stripe)" /><published>2025-12-10T15:00:00+01:00</published> <updated>2026-07-21T06:35:32+02:00</updated> <id>https://msicc.net/2025-12-10-leadessentials-main-composition-module-finished/</id> <author> <name>msicc</name> </author> <category term="Courses" /> <category term="LeadEssentials" /> <summary>In this post I share how the Lead Essentials Main Composition module helped me connect architecture, testing, modularization, and CI/CD into a more practical way of building scalable iOS apps.</summary> <content type="text">Over the past months, I’ve slowly but steadily worked my way through the Lead Essentials course. With this module, I’ve now reached the White Belt with its fourth stripe – and it really felt like one of those “things finally click” moments. This time, the focus was on Main Composition. If the earlier modules taught me how to think about architecture and testing, this one showed me how to actually plug everything together in a way that stays clean, testable, and ready for production. A Better Way to Compose Apps Before this module, I already tried to keep my projects somewhat organized, but the “composition layer” was never a clearly defined concept in my head. Here, it became obvious why it matters so much. The idea is simple but powerful: have a dedicated composition layer that is the only place where objects are created and wired together. View controllers, presenters, view models, services, and gateways all get assembled there – not spread across random initializers or factory methods in the app. Another big shift for me was treating Xcode modules, frameworks, Swift packages, and even directories as composable building blocks. Having used this kind of modularization in my C# projects before, it now feels great that I have learned to use that mindset in Swift and iOS development as well. Patterns That Make a Difference The course also explores several design patterns, but always in a practical, “use it when it solves a real problem” kind of way. I revisited the Strategy and Composite patterns, but this time with a stronger focus on how to integrate them cleanly into Swift codebases. One highlight was applying the Composition Root pattern. It’s a subtle but powerful idea: centralize all wiring of dependencies in one place, and keep the rest of the app clean. Combined with memory- and thread-management techniques for the composition layer, it becomes much easier to prevent common issues like leaks or inconsistent execution contexts. Testing From Every Angle As with the other modules, testing wasn’t an afterthought – it was baked into everything. The goal stayed the same: reduce complexity, speed up feedback, and increase confidence. UI testing UI tests suddenly became a lot less flaky once I learned how to take full control over app and network state. Using launch arguments and conditional compilation to put the app into predictable scenarios felt like finally getting a grip on something that used to be more “hope than strategy”. Acceptance testing One of the biggest mindset shifts for me was letting go of slow, end-to-end UI acceptance tests and replacing them with fast integration acceptance tests instead. At first, this felt a bit wrong – I was so used to thinking “end-to-end or nothing”. But after seeing how fast, stable, and focused these integration tests can be, I’m fully convinced. Snapshot testing Snapshot testing also leveled up. Instead of running the full app and hoping for consistent screenshots, the course showed how to render views in isolation and drive them directly. That alone makes snapshot tests much faster and less brittle. Dark Mode support was a good reminder as well. I tend to push it late in the process, and this module showed how easy it is to include it from the beginning when you have the right test setup. “Impossible” tests I also enjoyed the part about testing things that feel “impossible” to test: private methods, system classes, and behavior without nice public entry points. With the right architecture and a few Swift techniques, a lot of this becomes surprisingly manageable. Effective Use of Combine I’ve never used Combine before this course, so this was my first deep dive into it. The course did a great job of showing how Combine can simplify communication between components in a testable way. Instead of sprinkling custom callbacks, delegates, and completion handlers all over the place, you can lean on Combine to unify those patterns and remove duplication. Seeing how the same abstraction can sit underneath different architectural choices was a real eye-opener for me. CI, CD and App Store Delivery The lessons didn’t stop at the codebase itself. This module walks through modern CI/CD practices and shows how to integrate continuous delivery and deployment into a real iOS workflow. From local builds to pipelines that automatically ship a build to App Store Connect, everything is covered end-to-end. Even though I already use GitHub Actions heavily, the course thaught me new ways of doing CI, CD and deployment tasks and fitting them into a professional setup. Simulating the Full App Lifecycle A recurring theme in this module is understanding an app’s lifecycle — not from the perspective of the user, but from the perspective of tests. Learning how to simulate app launches, state transitions and even rare scenarios made these scenarios a lot clearer. These techniques will definitely influence my future projects, including the SwiftUI rewrite of TwistReader. Wrapping up This module was dense, no doubt about it. But it also pulled together a lot of threads from the earlier parts of the course: architecture, composition, modularization, testing, and delivery. Reaching this White Belt level feels like more than just unlocking the next badge. It feels like I’ve gained a much more practical, reality-proof way of thinking about how to build scalable and testable Swift apps. And of course, I’m already curious about what the remaining modules until the blue belt, which will be rewarded for completion of the course, will bring. Click to see the full iOS Lead Essentials curriculum Disclaimer: This blog post was written with the help of AI, based on a bulleted summary of learning topics provided as part of the Lead Essentials program. The structured list served as the foundation for turning the content into a more readable, narrative-style post that reflects my personal learning experience. I reviewed and edited the post before publishing to ensure it meets the quality standards of this blog.</content> </entry> <entry><title>My Journey to Passing the B1 Certification for Italian as a Foreign Language</title><link href="https://msicc.net/2025-11-14-my-journey-to-passing-the-b1-certification-italian/" rel="alternate" type="text/html" title="My Journey to Passing the B1 Certification for Italian as a Foreign Language" /><published>2025-11-14T16:00:00+01:00</published> <updated>2026-07-21T06:35:32+02:00</updated> <id>https://msicc.net/2025-11-14-my-journey-to-passing-the-b1-certification-italian/</id> <author> <name>msicc</name> </author> <category term="Courses" /> <category term="Learning" /> <category term="Exams" /> <summary>Back in October, I took the official Italian B1 exam from Università Roma Tre. After waiting several weeks, the results arrived — and I passed with 90/100.</summary> <content type="text">In October, I took the CERTIFICAZIONE DI ITALIANO L2 DI LIVELLO B1 DEL CONSIGLIO D’ EUROPA exam from the Università Roma Tre. It was the final point of a few months of consistent study, daily practice and a clear goal. Yesterday, the results finally arrived (ahed of the expected date in mid december) — and: I passed with 90/100. This post is a quick summary of why I took the exam, how I prepared, and what I learned along the way. Why I decided to take the exam Being married to an Italian wife, Italian has been part of my personal life for many years - but I always wanted a clear, official confirmation of my level (for several practical and personal reasons). Signing up for the exam gave me a fixed date — which immediately made the learning process more focused. From July until the exam date in early October, I created weekly routines around: grammar and vocabulary refreshers reading short articles and summarizing them listening to B1-level audio materials writing short messages and emails speaking practice (mostly monologues and exam role-plays) Nothing fancy — just steady work. Using ChatGPT Study Mode Since I have a busy schedule, I looked for a way to keep the process consistent. ChatGPT’s Study Mode helped me do exactly that. I mainly used it for: conversation practice: small talk, monologues, exam-style questions writing feedback: checking short texts and correcting recurring mistakes grammar refresh: quick exercises on topics like ci/ne or reflexive verbs light exam simulations: especially speaking prompts and summaries Study Mode didn’t replace textbooks or official exam material, but it made the daily routine easier and more structured — which was exactly what I needed. The exam day (October) The exam covered the usual four parts: Listening Reading Writing Speaking The tasks were fair and aligned with the official practice material. Thanks to the preparation, the speaking part — which I thought would be the hardest — went surprisingly well. Waiting for the results Waiting until yesterday wasn’t the most enjoyable part, but that’s how the process works. Receiving the email that the cerfitication results are available online finally ended the wait. Of course, I went to the online portal immediately to check my results — and seeing a 90/100 one was a great moment and closed the whole journey in a very positive way. What’s next? Right now, I’m happy with the result and will continue using Italian in my daily life. I might go for B2 at some point, but I’m not in a rush. First, I want to keep improving naturally, without exam pressure. Eventually, I am going to get my English certification as well, so I might use a similar approach there. But that’s a story for another time (maybe in 2026). Conclusion This certification has been a meaningful milestone of mine. It showed me (once again) that consistent small steps build real progress — even with a full schedule. If you are also learning a language: set a concrete goal, build a routine around it, and keep going. It works. Happy learning! Disclaimer: As ChatGPT supported me during my learning journey, it also helped me draft and organize this blog post (it knew all the important points, anyways). I reviewed and edited the content to ensure accuracy and personal voice. On top, I let ChatGPT generate the title image based on my prompts.</content> </entry> <entry><title>Share Copilot Prompts and Instructions Across Teams Using Rider and VS Code</title><link href="https://msicc.net/share-copilot-prompts-and-instruction-across-teams-using-rider-and-vscode/" rel="alternate" type="text/html" title="Share Copilot Prompts and Instructions Across Teams Using Rider and VS Code" /><published>2025-10-17T20:00:00+02:00</published> <updated>2026-07-21T06:35:32+02:00</updated> <id>https://msicc.net/share-copilot-prompts-and-instruction-across-teams-using-rider-and-vscode/</id> <author> <name>msicc</name> </author> <category term="Dev Stories" /> <category term="Tools" /> <category term="AI &amp;&amp; LLMs" /> <summary>Ensure GitHub Copilot instructions and prompts stay accessible in both Rider and VS Code by organizing your .NET repo&#39;s .github folder structure.</summary> <content type="text">Instructions and prompts It is no secret that I am using GitHub Copilot as my personal coding assistant. To optimize the quality of Copilot’s answers (and code), I recently started to use custom instructions and reusable prompts. What are instructions? Instructions are a set of customization commands we can give Copilot to specify our preferences. There are two main kinds of instructions: Repository-wide instructions These are valid in the context of the repo itself (like formatting, folder structure, commit preferences). These instructions live in the copilot-instructions.md file directly within the .github folder. A sample could look like this: 1 2 3 4 5 # C# Coding Standards - Use PascalCase for public members - Use async/await for I/O operations - Follow SOLID principles - Write XML documentation for public APIs Path-specific instructions These are valid in the context of a specific file path (like class files, XAML files, HTML files, etc.) and live in multiple NAME.instructions.md markdown files within the instructions subfolder. A sample could look like this: 1 2 3 4 # *.xaml.cs Instructions - Follow MVVM pattern for code-behind - Keep code-behind minimal, delegate to ViewModels - Use data binding instead of direct property access Both VS Code and Rider support both of these instruction types. What are prompts? Prompts are reusable instructions for specific use cases, such as refactoring code or generating tests. Like manual prompts, saved prompts follow the repository’s instructions. They are stored in markdown files within the .github/prompts folder. A sample prompt file (e.g., generate-unit-tests.prompt.md) could look like this: 1 2 3 4 5 6 7 # Generate Unit Tests Generate comprehensive unit tests for the selected code using xUnit. - Ensure 80% code coverage minimum - Use descriptive test names following the Given-When-Then pattern - Mock external dependencies - Include both positive and negative test cases We can then trigger these prompts via Copilot’s Chat / menu for quick access. Getting Started with Awesome Copilot To jumpstart your Copilot setup, I recommend using a personal fork of the ‘Awesome Copilot’ repository. The repository provides a curated collection of ready-to-use instructions and prompts that you can adapt to your team’s needs. You can either: Fork the repository and customize the files for your project Copy specific instructions/prompts that match your tech stack Use their browser-based installer for quick setup How to structure your solution The instructions and prompts are saved in subfolders within the .github folder, which is created automatically when you clone a GitHub repo. If you are using Copilot but another source control system like Bitbucket, you will need to create this folder manually. The .github folder should always live at the root of the repository’s folder. Once you have the .github folder, create both an instructions and a prompt folder, so your repo’s folder structure looks like this: 1 2 3 4 5 6 7 8 9 your-repo/ ├── .github/ │ ├── copilot-instructions.md │ ├── instructions/ │ │ └── *.instructions.md │ └── prompts/ │ └── *.prompt.md ├── YourSolution.sln └── src/ Tip: If you can’t see the .github folder in finder or file explorer, you need to activate hidden files and folders. Hit Command+Shift+. on macOS to show them. On Windows, you need to go to the file explorer options, select View and find the Show hidden files, folders and drives option in the Advanced settings section. Why this structure is important VSCode and Rider are opening .NET solutions differently. While VS Code always uses the folder approach and just adds a Solution Explorer view, but stays in the context of the folder, Rider needs to open the solution directly to work with it, even if you are choosing a folder. Because of this behavior, it is important to keep the .sln file always at the same level as the .github folder - which should ideally be the root folder of the repository. Only then you will be able to use both VS Code and Rider for your repository while keeping the instructions and prompts at hand. This interoperability is especially important if multiple persons work on the same repo as a team. Conclusion As you can see, we can keep interoperability of our .NET solutions easily just by following some easy conventions. If you follow this structure, you can make both VS Code and Rider following your instructions or use your prompts in the / menu of Copilot’s Chat. As always, I hope this post is helpful for some of you. Until the next post, happy coding, everyone!</content> </entry> <entry><title>Version Control Your Diagrams: Automated PlantUML Rendering with GitHub Actions</title><link href="https://msicc.net/version-control-your-diagrams-automated-plantuml-rendering-github-actions/" rel="alternate" type="text/html" title="Version Control Your Diagrams: Automated PlantUML Rendering with GitHub Actions" /><published>2025-07-21T20:00:00+02:00</published> <updated>2026-07-21T06:35:32+02:00</updated> <id>https://msicc.net/version-control-your-diagrams-automated-plantuml-rendering-github-actions/</id> <author> <name>msicc</name> </author> <category term="Dev Stories" /> <category term="Automation" /> <category term="Documentation" /> <summary>Learn how to automate PlantUML diagram generation using GitHub Actions to create SVG images for your documentation. This post demonstrates a complete workflow that renders diagrams from .puml files and commits them back to your repository automatically.</summary> <content type="text">My Motivation A few months ago I joined the Architecture Guild at work to participate in the architectural decision process as well as to educate myself further in this area. As part of this additional role, I have taken over the responsibility of certain tasks in our team. One of them is documentation — specifically, how to improve the existing documentation by following a Docs As Code approach and using established tools like AsciiDoc and PlantUML. However, for my private projects, I haven’t had the chance to utilize such tools yet. For my private side projects, I am shifting my focus towards Swift and SwiftUI for mobile, tablet and desktop applications. To support my journey in this area, I am also working through the LeadEssentials iOS learning program. In this program, we learn best practices, tools and techniques for developing iOS applications with a focus on enterprise applications. From my experience, many of these best practices and tools can also be used for side projects to some extent. You may have heard about my FeedReader app TwistReader, which is currently on TestFlight. The current version is a MAUI app, and the current state is still “beta”. To push it through to the App Store, I will have to rework a good amount of the existing codebase to improve and make the app run more reliably. I stopped developing it seriously some time ago because it was “good enough” for me and my few beta testers. This means there are some annoyances as well, and to iron them out, I will have to put quite a bit of effort into it again. All of these factors led me to decide that TwistReader will be my side project to apply what I have learned. This means a complete rewrite of the application in Swift and SwiftUI! Before writing a single line of code, I applied the Docs As Code approach I learned at work and created a C4 model architecture diagram for the first three levels. The docs will be hosted on GitHub, which allows me to create a Wiki or documentation website if needed. For creating the diagrams, I chose PlantUML, a descriptive tool that generates diagrams from code. For the documents themselves, I stayed with Markdown because it is better supported on GitHub than AsciiDoc. In this post, I will show you a basic diagram and how I automate the rendering process with a GitHub action. Sample repository I created a sample repository that demonstrates the whole process. You can find it here on GitHub. I prepared a docs folder in the repo as this is a typical approach when following the Docs As Code approach, keeping documentation organized and version-controlled alongside the source code. This folder sits on the root level just as the src or tests folder. Within this folder, I created a .puml file that demonstrates the automated diagram generation process we’ll explore in this post. In the .github/workflows folder, I created the YAML configuration file that makes this automation work. We will go through this file next to understand how the automation works. Automating the rendering process Configure the triggers Let’s start with the trigger section of the YAML file: 1 2 3 4 5 6 7 8 on: push: branches: - docs paths: - &#39;docs/*.puml&#39; workflow_dispatch: # manual trigger via GitHub UI This configuration tells the GitHub Action runner to only execute on pushes to the docs branch. The paths tell the action to only run when a .puml has changed. Tip: If you have sub-folders, just insert /** after the docs/. The workflow_dispatch command entry allows us to trigger the action manually as well. Good to know: The action must have run successfully at least once before we can trigger the action manually. Configure the Job 1 2 3 4 5 jobs: plantuml: runs-on: ubuntu-latest permissions: contents: write # Required to push changes back to the repository With these lines, we are telling the runner to run our job named plantuml on a Linux VM. We are also setting up the permissions for the default GitHub secret, which will be needed to commit the generated images back into our repository. Now we are ready to configure the steps of our action: Checkout The first step is - no surprise - to check out the repo. The important addition in this step is the token configuration, which will enable the action to auto-commit the generated images back to the repository. 1 2 3 4 - name: Checkout uses: actions/checkout@v3 with: token: $ Install Java and GraphViz The next two steps are prerequisites for PlantUML: 1 2 3 4 5 6 7 8 - name: Set up Java uses: actions/setup-java@v4 with: distribution: &#39;temurin&#39; java-version: &#39;17&#39; - name: Install GraphViz run: sudo apt-get update &amp;&amp; sudo apt-get install -y graphviz Download PlantUML Finally, we are going to download PlantUML. I needed to use a specific version instead of the latest tagged version to get my GitHub action up and running: 1 2 3 4 5 6 - name: Download PlantUML jar run: curl -L -o plantuml.jar https://github.com/plantuml/plantuml/releases/download/v1.2024.7/plantuml-1.2024.7.jar # Optionally check the SHA256 checksum to avoid corrupt downloads # - name: Verify PlantUML jar checksum # run: echo &quot;&lt;expected_sha256_here&gt; plantuml.jar&quot; | sha256sum -c - Generate the .svg image(s) Now that PlantUML is available on our runner, we are finally able to render the .svg images for all .puml files found in the docs folder (and sub-folders, if any): 1 2 3 - name: Render .puml to .svg run: | find ./docs -name &#39;*.puml&#39; -exec java -jar plantuml.jar -tsvg -o . {} + Commit the generated image Of course, we want to use the image(s) in our repository. That’s why we are going to commit the generated image(s) into our repo: 1 2 3 4 5 6 7 8 9 10 11 12 - name: Commit rendered diagrams run: | git config --global user.name &quot;github-actions[bot]&quot; git config --global user.email &quot;github-actions[bot]@users.noreply.github.com&quot; git add docs/*.svg if git diff --cached --quiet; then echo &quot;No diagram changes to commit.&quot; else git commit -m &quot;docs: auto-rendered PlantUML diagrams&quot; git push fi Conclusion Just as we developers version control our code, we can also version control diagrams used in our documentation, wikis, or blog posts by saving them in our repositories. We can also use tools like PlantUML to describe the diagram with a markup language and use GitHub Actions to render the resulting diagram images. This process can be easily automated and ensures our diagrams are properly versioned alongside our code. What’s your experience with Docs as Code? I’m curious which tools you’re using and whether you’ve automated diagram generation like this. Let’s discuss - drop a comment or reach out on social media! As always, I hope this blog post is helpful for some of you. Until the next post, happy coding, everyone! The title image is the generated image from the sample repository for this blog post.</content> </entry> <entry><title>Lead Essentials: UI + Presentation Module finished (White Belt 3rd stripe)</title><link href="https://msicc.net/2025-07-13-leadessentials-ui-presentation-module-finished/" rel="alternate" type="text/html" title="Lead Essentials: UI + Presentation Module finished (White Belt 3rd stripe)" /><published>2025-07-13T16:00:00+02:00</published> <updated>2026-07-21T06:35:32+02:00</updated> <id>https://msicc.net/2025-07-13-leadessentials-ui-presentation-module-finished/</id> <author> <name>msicc</name> </author> <category term="Courses" /> <category term="LeadEssentials" /> <summary>In this post, I share what I learned while earning the White Belt 3rd stripe in the Lead Essentials program, focusing on clean, testable UI and presentation architecture in Swift.</summary> <content type="text">It’s time for another update on my journey through the iOS Lead Essentials program. The course continues to challenge and shape the way I approach app architecture and development. With the recent completion of the fourth module, I’ve now achieved the White Belt 3rd stripe — and with it, gained a deeper understanding of building clean and testable UI and Presentation layers in Swift. This module was all about the interface between users and our code: how data is presented on screen, how user input is handled, and how to structure this part of an app in a scalable, testable, and maintainable way. Understanding UI, UX, and Presentation Responsibilities We started with fundamentals: what makes good UI and UX, how to structure the presentation layer for testability, and how to collaborate effectively with designers. While some of this was familiar territory, the practical tips on validating UI design through fast prototyping, and getting early feedback before investing too much time, were immediately useful. Another highlight was refining how we load and present data efficiently — especially images, which can be a major performance bottleneck if handled poorly. I learned how to prefetch images as cells become visible, cancel unnecessary requests to reduce data usage, and apply these strategies using reusable UI components. From MVC to MVVM and MVP — and Beyond This module also dove deep into architectural patterns. We revisited MVC, looking at both best practices and common pitfalls — particularly the infamous Massive View Controller problem. The course offered strategies for breaking down complex scenes using smaller, collaborative MVCs. From there, we moved on to MVVM and MVP, each with their own strengths and common misuses. Identifying massive view models or presenters was eye-opening, as was the idea of combining both patterns with platform-agnostic components. The hands-on approach of test-driving each pattern in Swift gave me clarity on how and when to apply them. I also learned to implement both stateful and stateless view models — something I hadn’t used to my advantage before. Making the Most of Xcode and Swift The course didn’t stop at patterns. I spent time learning how to organize my codebase for reuse across platforms. By separating platform-specific and platform-agnostic code using Swift frameworks in Xcode, and learning how to properly support multiple platforms. Retain cycles can silently harm app performance, so learning how to spot and eliminate them using the Proxy design pattern was another valuable lesson. Similarly, I learned more about the Adapter and Decorator patterns and saw how they can help bridge incompatible components or extend object behavior in a clean, testable way. Testing and Refactoring with Confidence Throughout the module, there was a strong emphasis on maintaining high-quality, testable code. I explored both inside-out and outside-in development strategies, and how Swift’s type system and compiler can back up safe refactorings. I also learned how to safely add tests to legacy code — something every long-running project eventually needs. Finally, the module wrapped up with techniques for creating reusable components using generics in Swift, and the importance of test-driving localized strings to ensure customer-facing features work as expected in every supported language. What’s Next? I’ll continue progressing through the remaining belt levels in the Lead Essentials curriculum. The journey so far has already leveled up my thinking as a developer, and I’m looking forward to level up further as the course progresses. Click to see the full iOS Lead Essentials curriculum Disclaimer: This blog post was written with the help of AI, based on a bulleted summary of learning topics provided as part of the Lead Essentials program. The structured list served as the foundation for turning the content into a more readable, narrative-style post that reflects my personal learning experience. I reviewed and edited the post before publishing to ensure it meets the quality standards of this blog.</content> </entry> <entry><title>Integrating Sentry in .NET MAUI with Local File Logging</title><link href="https://msicc.net/integrating-sentry-in-dotnet-maui-with-local-file-logging/" rel="alternate" type="text/html" title="Integrating Sentry in .NET MAUI with Local File Logging" /><published>2025-05-06T12:00:00+02:00</published> <updated>2026-07-21T06:35:32+02:00</updated> <id>https://msicc.net/integrating-sentry-in-dotnet-maui-with-local-file-logging/</id> <author> <name>msicc</name> </author> <category term="Dev Stories" /> <category term="MAUI" /> <summary>Learn how to integrate Sentry into your .NET MAUI app with structured local file logging — enabling full diagnostics even when cloud reporting is disabled. Perfect for privacy-conscious apps and real-world debugging.</summary> <content type="text">Sentry is a powerful tool for error tracking and diagnostics, and in my TwistReader MAUI app, I wanted to go beyond just capturing errors in production. The goal: use Sentry for all logging, but persist the data locally unless the user explicitly allows cloud reporting. This post walks you through the full implementation — from capturing events and enriching them with device context, to storing Sentry event data in structured JSON using the NReco.Logging.File package for proper file handling. Why combine Sentry and local logging? In real-world mobile apps, especially privacy-conscious ones, we often can’t (or don’t want to) send every crash and log event to the cloud. But we still need diagnostics — especially when users report problems manually. Sentry already collects an amazing amount of context: Device and OS info App version Network requests Breadcrumbs (aka event history) So the idea was simple: reuse this rich Sentry event data for all application diagnostics - no matter if the user allows sending the data to the cloud or not. In my case, the trigger was a classic support situation: a user reported a bug I couldn’t reproduce myself. Without logs, debugging was pure speculation — with locally stored Sentry events, I was able to trace the issue exactly, even though the user had disabled cloud reporting. This is where this setup really shines. Setup The Nuget packages First, we need to install some Nuget packages: dotnet add package Sentry.Maui dotnet add package NReco.Logging.File I chose NReco.Logging.File because it’s easy to configure, works reliably across platforms, and doesn’t introduce platform-specific quirks. Configure the File Logger Let’s setup our file logger next. Here is my configuration in the MAUI application builder: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 builder.Services.AddLogging(loggingBuilder =&gt; { var logFilePath = System.IO.Path.Combine(FileSystem.Current.CacheDirectory, &quot;twistReader.log&quot;); loggingBuilder.AddFile(logFilePath, fileLoggerOpts =&gt; { fileLoggerOpts.FormatLogFileName = new Func&lt;string, string&gt;(fName =&gt; string.Format(fName, DateTime.UtcNow)); fileLoggerOpts.MaxRollingFiles = 5; fileLoggerOpts.Append = true; fileLoggerOpts.FileSizeLimitBytes = 10_000_000; fileLoggerOpts.RollingFilesConvention = FileLoggerOptions.FileRollingConvention.Descending; fileLoggerOpts.MinLevel = LogLevel.Information; }); }); This configuration ensures that our log files do not grow exponentially. I’m using the CacheDirectory here to ensure temporary but persistent logging during the app’s runtime lifecycle. Depending on your use case, storing logs in AppDataDirectory might be a better fit — especially if you want them to survive app reinstalls. Logging Interface My app already uses ILogger&lt;T&gt;, so I wrapped the logic in a ISentryEventLogger abstraction: 1 2 3 4 public interface ISentryEventLogger { void LogEvent(SentryEvent sentryEvent); } Implementation Here’s the concrete implementation: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 public class SentryEventLogger : ISentryEventLogger { private readonly ILogger&lt;SentryEventLogger&gt; _logger; private readonly ISettingsService _settingsService; private readonly string _appVersion; private readonly string _platform; public SentryEventLogger(ILogger&lt;SentryEventLogger&gt; logger, ISettingsService settingsService) { _logger = logger; _settingsService = settingsService; _appVersion = AppInfo.VersionString; #if ANDROID _platform = &quot;Android&quot;; #elif IOS _platform = &quot;iOS&quot;; #elif MACCATALYST _platform = &quot;MacCatalyst&quot;; #else _platform = &quot;Unknown&quot;; #endif } public void LogEvent(SentryEvent sentryEvent) { if (sentryEvent == null) return; sentryEvent.SetTag(&quot;app.version&quot;, _appVersion); sentryEvent.SetTag(&quot;platform&quot;, _platform); sentryEvent.SetTag(&quot;cloud.logging.allowed&quot;, _settingsService.ShouldAllowCloudLogging.ToString()); var logObject = new { Timestamp = sentryEvent.Timestamp, Level = sentryEvent.Level?.ToString(), Logger = sentryEvent.Logger, Message = sentryEvent.Message?.Message, Exception = sentryEvent.Exception?.ToString(), User = sentryEvent.User, Tags = sentryEvent.Tags, Contexts = sentryEvent.Contexts, Extra = sentryEvent.Extra, Request = sentryEvent.Request != null ? new { sentryEvent.Request.Method, sentryEvent.Request.Url, sentryEvent.Request.QueryString, sentryEvent.Request.Headers, sentryEvent.Request.Cookies, sentryEvent.Request.Data } : null, Breadcrumbs = sentryEvent.Breadcrumbs?.Select(b =&gt; new { b.Timestamp, b.Level, b.Category, b.Type, b.Message, b.Data }).ToList() }; var json = JsonSerializer.Serialize(logObject, new JsonSerializerOptions { WriteIndented = false, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); var level = MapSentryLevelToLogLevel(sentryEvent.Level ?? SentryLevel.Error); _logger.Log(level, &quot;[Sentry] {Json}&quot;, json); } private static LogLevel MapSentryLevelToLogLevel(SentryLevel level) { return level switch { SentryLevel.Debug =&gt; LogLevel.Debug, SentryLevel.Info =&gt; LogLevel.Information, SentryLevel.Warning =&gt; LogLevel.Warning, SentryLevel.Error =&gt; LogLevel.Error, SentryLevel.Fatal =&gt; LogLevel.Critical, _ =&gt; LogLevel.Error }; } } This ensures all captured events — even if not sent to Sentry — are logged in a clean, structured way locally. Optional cloud forwarding In my Sentry options, I use the SetBeforeSend event to check whether cloud reporting is allowed: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 builder.UseSentry(options =&gt; { options.Dsn = &quot;your dsn here&quot;; options.SetBeforeSend(sentryEvent =&gt; { try { var isInternalLoop = sentryEvent.Logger?.Contains(nameof(SentryEventLogger)) == true; if (isInternalLoop) return null; var serviceProvider = builder.Services.BuildServiceProvider(); var settingsService = serviceProvider.GetRequiredService&lt;ISettingsService&gt;(); var sentryEventLogger = serviceProvider.GetRequiredService&lt;ISentryEventLogger&gt;(); sentryEventLogger.LogEvent(sentryEvent); return settingsService.AllowCloudLogging ? sentryEvent : null; } catch (Exception ex) { Debug.WriteLine($&quot;Failed to process Sentry event: {ex.Message}&quot;); return null; } }); }); Replace &quot;your dsn here&quot; with your actual Sentry DSN. You may have noticed the isInternalLoop check. This is needed to suppress log entries coming from the SentryEventLogger class itself. If you do not filter these events, you will run into a StackOverflowException pretty fast. Trust me, I “tested” that for you already. Now all that is left is to register the service with the ServiceProvider: 1 builder.Services.AddSingleton&lt;ISentryEventLogger, SentryEventLogger&gt;(); Conclusion If you’re building a .NET MAUI app and need flexible error tracking — I highly recommend this approach. You get the full power of Sentry’s event model, but stay in control of how and where the data goes. This setup works great as a foundation for a consent-based logging system — especially useful when building cross-platform MAUI apps with privacy in mind. As always, I hope this blog post is helpful for some of you. Until the next post, happy coding, everyone! The title image is generated with AI (ChatGPT) based on my prompts.</content> </entry> <entry><title>Lead Essentials: Persistence Module finished (White Belt 2nd stripe)</title><link href="https://msicc.net/2025-04-18-leadessentials-persistence-module-finished/" rel="alternate" type="text/html" title="Lead Essentials: Persistence Module finished (White Belt 2nd stripe)" /><published>2025-04-18T16:00:00+02:00</published> <updated>2026-07-21T06:35:32+02:00</updated> <id>https://msicc.net/2025-04-18-leadessentials-persistence-module-finished/</id> <author> <name>msicc</name> </author> <category term="Courses" /> <category term="LeadEssentials" /> <summary>In this post, I share what I learned about persistence, concurrency, and scalable architecture while working through the third module of the iOS Lead Essentials course.</summary> <content type="text">After reaching the White Belt 1 stripe in the iOS Lead Essentials program, I’m starting to see how everything I’ve learned so far is beginning to connect. This third module, focused on persistence and system behavior, felt like the moment where theory and practice started to merge. Understanding Where Data Belongs It all started with a seemingly simple question: “Where and how should data live in an app?” - From that point on, the module dug into persistence options available in Swift—and more importantly, how to choose between them. I had already worked with things like UserDefaults, FileManager, and Core Data before—but this time, it wasn’t just about using these tools. It was about understanding why and when to use each one, how to test them effectively, and how to keep their usage clean and maintainable over time. Putting Persistence into Practice Working with URLCache, Codable plus the file system, and Core Data became a practical exercise in trade-offs. The course walked through how to persist and retrieve data using each approach and what implications that has for architecture, performance, and testing. Core Data, in particular, got a deep treatment—everything from modeling entities and managing contexts to dealing with concurrency and writing reliable tests. It was about more than just CRUD operations—it was about doing it right in real-world scenarios. Concurrency Without the Chaos Concurrency in Swift is one of those areas that always seems straightforward—until it’s not. This module dove into the details of DispatchQueue, serial vs. concurrent queues, and how to properly use sync, async, and barrier flags. But beyond the APIs, it challenged me to think more deeply: How do I keep shared state safe? How do I design components that behave correctly under load? I learned how to identify, debug, and prevent race conditions—and how Swift’s value and reference types behave in multithreaded contexts. Bringing Principles into Practice As things moved from implementation into design, the course introduced architectural principles that now feel essential. The Composite Reuse Principle (favoring composition over inheritance) took on new meaning through Swift’s protocol-oriented capabilities. Protocol inheritance, extensions, and conditional conformance all showed how modular, scalable code can be achieved without overly complex hierarchies. Architecture That Scales with Confidence Beyond principles, the module also offered architectural strategies that help when projects grow. Decoupling business logic from infrastructure, structuring deterministic logic, breaking monoliths into modules, and using Data Transfer Objects (DTOs) were all covered with practical Swift examples. Testability From the Ground Up Testing wasn’t treated as a separate topic. As the course is teaching us everything with a TDD approach, it was built into everything. I picked up strategies for controlling the environment during tests, measuring and optimizing test time with xcodebuild, and writing fast, stable tests through triangulation. The module also emphasized using real frameworks for integration testing instead of relying solely on mocks. Building a Maintainable Codebase Toward the end, the focus shifted to long-term code health. That included practical guidance on producing a clean and stable Git history, flattening hard-to-read nested logic (a.k.a. “arrow code”), and the importance of naming things well—something that’s often overlooked but makes a huge difference in collaboration and clarity. Wrapping It All Together This module was dense but incredibly rewarding. It deepened my understanding of how to manage data, deal with concurrency, and design Swift code that’s testable, maintainable, and scalable. A lot of the lessons felt connected to real problems I’ve encountered in the past. What’s Next? I’ll continue progressing through the remaining belt levels in the Lead Essentials curriculum. The journey so far has already leveled up my thinking as a developer, and I’m looking forward to level up further as the course progresses. Click to see the full iOS Lead Essentials curriculum Disclaimer: This blog post was written with the help of AI, based on a bulleted summary of learning topics provided as part of the Lead Essentials program. The structured list served as the foundation for turning the content into a more readable, narrative-style post that reflects my personal learning experience. I reviewed and edited the post before publishing to ensure it meets the quality standards of this blog.</content> </entry> <entry><title>How I migrated my Mastodon account to micro.blog</title><link href="https://msicc.net/migrating-my-mastodon-account-micro-blog/" rel="alternate" type="text/html" title="How I migrated my Mastodon account to micro.blog" /><published>2025-04-04T10:00:00+02:00</published> <updated>2025-05-23T07:13:19+02:00</updated> <id>https://msicc.net/migrating-my-mastodon-account-micro-blog/</id> <author> <name>msicc</name> </author> <category term="Archive" /> <summary>Migrating from Mastodon to micro.blog while keeping your Fediverse connections can be seamless. Here’s how I did it.</summary> <content type="text">Update 2025-05-23 I decided to move away from micro.blog because, as an indie developer, reachability is crucial for me. I noticed that the platform’s lack of notifications for direct messages and limited visibility of user interactions caused me to miss several important messages from users regarding my apps. This led to delays in addressing their concerns and providing support. While micro.blog offers a unique and minimalistic approach, it ultimately did not meet my needs for effective communication. I am keeping this guide available for those who find the platform suitable for their use case, but I have moved it to the Archive category. Over the past few weeks, I’ve been exploring micro.blog more and more. Eventually, I decided to fully migrate my Mastodon account over to it. Since both platforms support the Fediverse, the process was smoother than I expected. If you’re thinking about doing the same, here’s a step-by-step guide based on how I did it. What changes after migrating? Migrating from Mastodon to micro.blog comes with a few key consequences you should be aware of: Your Mastodon followers will be redirected to your new account, but not all of them may follow you back automatically. Any content you posted on Mastodon will not transfer to micro.blog—you’re starting fresh. Your interactions (likes, boosts, replies) also won’t migrate. If you used cross-posting from your blog to Mastodon, you’ll need to disable or reconfigure it. Some users might not recognize your new account handle immediately, especially if they don’t check notifications regularly. While none of these are dealbreakers for most, it’s good to know what to expect before starting the process. Key differences between micro.blog and Mastodon While both platforms are part of the Fediverse and support ActivityPub, they have distinct approaches: Focus: Mastodon is centered around short-form social posts in a timeline, while micro.blog blends social interaction with blogging—prioritizing content ownership and long-form writing. UI &amp; UX: Mastodon resembles traditional social networks like Twitter/X with boosts, likes, and trending posts. micro.blog offers a calmer, more minimal interface with no likes or algorithmic timelines. Content Types: micro.blog supports both short posts (microblogs) and full blog posts natively. Mastodon is geared toward short posts (500–1000 characters depending on the server). Hosting &amp; Domains: micro.blog offers built-in blog hosting with custom domain support. Mastodon typically requires third-party tools or self-hosting for blogging. Interaction: Mastodon offers more engagement mechanisms (e.g., boosts, favorites), whereas micro.blog emphasizes replies and conversations over vanity metrics. Community: Mastodon communities are often centered around specific servers (instances), while micro.blog has a single, unified community interface. Understanding these differences can help you decide if micro.blog is the right fit or how you’ll adjust once you’ve migrated. Step 1: Export Your Mastodon Followings First, you’ll want to preserve the list of people you follow. On Mastodon: Go to Preferences → Import and Export → Data Export. Click the Download button next to the Follows section to get your following.csv file. Step 2: Import Followings to micro.blog Now switch over to your micro.blog account: Go to Account → View Fediverse Details. Scroll down to Import Follows and upload the following.csv file. After that, I waited a few days and only interacted with Mastodon users through micro.blog. Everything seemed to work just fine. Step 3: Set Up Account Migration Once your followings are successfully imported, it’s time to migrate your actual identity. On micro.blog: Navigate to Account → View Fediverse Details. Under Aliases, click Add Alias. Enter your old Mastodon handle and click Add Alias. Also, don’t forget to disable cross-posting to Mastodon to avoid strange problems: Scroll down to Feeds. Click Edit Source &amp; Cross-posting. Disable the Mastodon option. On Mastodon: Go to Preferences → Account. Scroll down to Move to a different account and click Configure it here. In the New account handle field, enter your micro.blog Fediverse handle (e.g., @yourname@micro.blog). Enter your password and click Move Followers. The migration might take a while, so be patient. Some followers will transfer instantly, while others may take hours or even a couple of days. Optional: Request Your Archive Before deleting or deactivating your Mastodon account, it’s a good idea to grab your archive: Go to Import and Export → Data Export. Click Request Your Archive. Conclusion With just a bit of prep work, you can smoothly transition from Mastodon to micro.blog and keep your Fediverse relationships intact. Are you thinking about migrating too? I’d love to hear your experience—feel free to reply or reach out. Disclaimer: This blog post was originally based on my rough notes, which were refined and structured with the help of AI to enhance readability and clarity. I then went through the whole post again making sure everything is correct and updated it where necessary. Additionally, the title image for this post was generated using AI.</content> </entry> <entry><title>CI-Ready macOS Signing: Combining Apple Distribution &amp; Installer Certificates for GitHub Actions</title><link href="https://msicc.net/ci-ready-macos-signing-combining-certs-for-github-actions/" rel="alternate" type="text/html" title="CI-Ready macOS Signing: Combining Apple Distribution &amp; Installer Certificates for GitHub Actions" /><published>2025-03-27T15:00:00+01:00</published> <updated>2025-11-14T15:50:04+01:00</updated> <id>https://msicc.net/ci-ready-macos-signing-combining-certs-for-github-actions/</id> <author> <name>msicc</name> </author> <category term="Dev Stories" /> <category term="iOS" /> <category term="macOS" /> <category term="MAUI" /> <category term="Automation" /> <summary>Setting up CI/CD for macOS apps on GitHub Actions comes with its own set of gotchas — especially when Apple requires not one, but two certificates for a complete signing workflow. This post documents how I resolved certificate import issues in CI and got everything working for macOS distribution.</summary> <content type="text">In my previous blog post, Exporting Apple Distribution Certificates for CI/CD — The Right Way, I shared a reliable method to extract and repackage Apple Distribution certificates into a clean .p12 file for use in GitHub Actions — avoiding legacy encryption pitfalls and ensuring compatibility across CI environments. That approach worked perfectly for iOS signing, but when I tried to create a pipeline that supports macOS Catalyst apps, I ran into another requirement I forgot: Apple mandates an additional certificate — the 3rd Party Mac Developer Installer certificate — for signing the installer package used in notarization or App Store submission. The problem? Importing these two certificates separately into GitHub Actions failed — not because of the signing itself, but because of how the certificate import action manages keychains. In this post, I’ll show how combining both certificates into a single .p12 file was not only necessary but the only way to make the macOS signing process fully work in CI. I’ll also walk through the exact OpenSSL steps that did the trick. Why You Need Two Certificates for macOS Distribution To successfully sign and distribute a macOS app via TestFlight or the Mac App Store, you need: Apple Distribution certificate – used to code sign the application binary. 3rd Party Mac Developer Installer certificate – used to sign the .pkg installer required for notarization and App Store Connect submission. Both are mandatory. If either is missing or unavailable, your CI/CD pipeline will fail during signing or upload. Why Importing Certificates Separately Didn’t Work in GitHub Actions Initially, I tried importing each certificate using separate steps with apple-actions/import-codesign-certs. Both .p12 files were valid and correctly configured as GitHub secrets. However, the issue arose because the action creates a new temporary keychain each time it runs, using the same name by default. Here’s what happened: On the first import (Apple Distribution), the keychain was created and the certificate was successfully imported. On the second import (Installer), the action attempted to create the same keychain again — but since it already existed, the step failed with an error like: 1 A keychain with the same name already exists As a result, the second certificate was never imported, and the workflow failed. Setting create-keychain: false did also not help — the action still tried to manage the keychain somehow and ran into the same conflict. While importing both certificates separately did work, importing them together in a GitHub Action did not work due to the isolated keychain management behavior of the action. The Solution: Combine Both Certificates Into One .p12 Rather than trying to work around the keychain creation behavior, the solution was simple and effective: combine both certificates into a single .p12 file. This allowed me to: Import both certificates in a single step Avoid keychain collisions entirely Ensure both certs are available in the same keychain context How I Combined the Certificates Export each certificate from Keychain Access (following the approach in my previous post): AppleDistribution.p12 InstallerCert.p12 Extract the certs and keys using OpenSSL: 1 2 openssl pkcs12 -in AppleDistribution.p12 -out AppleDistribution.pem -nodes openssl pkcs12 -in InstallerCert.p12 -out InstallerCert.pem -nodes Combine both into one PEM file: 1 cat AppleDistribution.pem InstallerCert.pem &gt; CombinedCerts.pem Export combined .p12: 1 2 3 4 5 openssl pkcs12 -export \ -in CombinedCerts.pem \ -out TimeTraverseHub_CompleteCerts.p12 \ -keypbe AES-256-CBC \ -certpbe AES-256-CBC Base64 encode for GitHub Actions: 1 base64 -i TimeTraverseHub_CompleteCerts.p12 -o TimeTraverseHub_Certs_base64.txt Verifying Import in CI To confirm that both certificates were successfully imported into the keychain during your GitHub Actions workflow, you can use the following step to inspect their metadata: 1 2 3 4 - name: Show certificates info run: | security find-certificate -a -c &quot;Apple Distribution&quot; -p | openssl x509 -noout -subject -issuer -dates security find-certificate -a -c &quot;Installer&quot; -p | openssl x509 -noout -subject -issuer -dates This will output the subject, issuer, and validity dates for both certificates — helping you confirm their presence and trust level at runtime. Results After switching to the combined .p12 approach: Both certificates imported successfully in a single step Code signing and installer signing worked end-to-end Upload to App Store Connect via CI succeeded without errors Conclusion This wasn’t just an optimization — it was the only reliable way to get both certificates working in GitHub Actions. The keychain behavior of import-codesign-certs prevents multiple independent imports from succeeding in the same workflow run. If you’re building and distributing macOS (or Catalyst) apps in CI, I highly recommend combining your Apple Distribution and Installer certificates into one .p12. It solves the above described problems and ensures a smooth, repeatable pipeline. Ever ran into the same problem? How did you solve this issue? Was this blog post helpful for you? Let’s share experiences, I am curios to read how it went for you! Until the next post, happy coding, everyone! Disclaimer: As with the previous post, I had some help from ChatGPT to get to the root of how to solve this (instead of searching the web) and shape this blog post. The final content is mine, but for the troubleshooting part, ChatGPT was an invaluable help. Additionally, the title image for this post was generated using AI.</content> </entry> <entry><title>Exporting Apple Distribution Certificates for CI/CD the Right Way</title><link href="https://msicc.net/exporting-apple-distribution-certificates-for-cicd-the-right-way/" rel="alternate" type="text/html" title="Exporting Apple Distribution Certificates for CI/CD the Right Way" /><published>2025-03-22T07:00:00+01:00</published> <updated>2026-07-21T06:34:54+02:00</updated> <id>https://msicc.net/exporting-apple-distribution-certificates-for-cicd-the-right-way/</id> <author> <name>msicc</name> </author> <category term="Dev Stories" /> <category term="iOS" /> <category term="macOS" /> <category term="MAUI" /> <category term="Automation" /> <summary>Working with Apple’s code signing system in CI/CD setups can be frustrating — especially when legacy encryption trips up modern tooling like OpenSSL 3. In this post, I’m walking through the exact steps I used to extract and repackage my Apple Distribution certificate into a .p12 that works flawlessly with GitHub Actions. If you’ve hit the infamous RC2-40-CBC error, this guide will help you get ...</summary> <content type="text">When you’re setting up CI/CD for iOS projects using GitHub Actions, one of the first real blockers is code signing. In particular, if you’re trying to use an exported .p12 file of your Apple Distribution certificate, chances are high you will run into an error that no valid code signing identity has been found in the keychain of your GitHub action. If you output valid signing identities (which you should only for debugging purposes), you might see something like this: 1 2 3 4 5 6 Run security find-identity -p codesigning -v 0 valid identities found [a-guid].mobileprovision [a-guid].mobileprovision [a-guid].mobileprovision [a-guid].mobileprovision This happened to me. I exported my distribution certificate from my Mac, converted it to a base64 string and saved it into a GitHub action secret. I repeated the process, only to see the same result again - no valid signing certificate available, even though I provided it. The Problem with .p12 Exported from Keychain Access In the end, I asked ChatGPT to help me troubleshoot this problem instead of just searching the web. ChatGPT first recommended me to run this command on the exported .p12 file: 1 openssl pkcs12 -in your.p12 -info -nodes And I saw this result: 1 2 3 4 5 MAC: sha1, Iteration 1 MAC length: 20, salt length: 8 PKCS7 Encrypted data: pbeWithSHA1And40BitRC2-CBC, Iteration 2048 Error outputting keys and certificates 400847EC01000000:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:crypto/evp/evp_fetch.c:355:Global default library context, Algorithm (RC2-40-CBC : 0), Properties () As you can see, the export used the RC2-40-CBC cipher, which has been deprecated for good reasons — but macOS still exports .p12 files with it by default. Why does this matter? Modern versions of OpenSSL 3.0 and later — used by macOS and GitHub Actions runners — have disabled support for RC2 by default, because it is considered insecure and obsolete. From the OpenSSL 3.0 release notes: “Some cryptographic algorithms that were previously included in the default provider are now only available in the legacy provider. These include RC2, RC4, and others.” OpenSSL 3.0 Legacy Algorithms Guide So if you’re getting this error, it’s not your fault — it’s OpenSSL being (understandably) picky for security reasons. When Did This Break? If you’re wondering like me when this became a problem: it started with the release of OpenSSL 3.0, back in September 2021. That version introduced the concept of “providers” and moved several legacy algorithms — including RC2, RC4, and MD2 — into the legacy provider, which is disabled by default. GitHub Actions runners (macos-12 and later) now use OpenSSL 3.x on macOS by default, so any .p12 file encrypted with RC2 (like those exported from macOS Keychain) will no longer be accepted unless you enable legacy support — or, better yet, repackage your cert with a supported cipher. OpenSSL 3.0 release notes The Solution: Repackage the Certificate Manually The fix for the above problem is to extract the certificate and private key from the RC2-encrypted .p12, and then repackage them into a new .p12 using modern encryption. Step 1: Extract PEM Files Using OpenSSL in Legacy Mode Export your original .p12 from Keychain Access, and name it something like apple_dist_raw.p12. Then, use OpenSSL with the -legacy flag to bypass the RC2 issue: 1 2 openssl pkcs12 -in apple_dist_raw.p12 -clcerts -nokeys -out cert.pem -legacy openssl pkcs12 -in apple_dist_raw.p12 -nocerts -nodes -out key.pem -legacy You’ll be prompted for your export password — use the one you originally set. If everything works, you now have: cert.pem — the distribution certificate key.pem — your private key in plain text Step 2: Repackage into a Clean .p12 Now, combine both into a fresh .p12 that avoids RC2 entirely: 1 2 3 4 5 6 openssl pkcs12 -export \ -inkey key.pem \ -in cert.pem \ -out fixed_cert.p12 \ -name &quot;Apple Distribution: [YOUR (TEAM) NAME] (YOUR TEAM ID)&quot; \ -passout &quot;pass:[YOUR_EXPORT_PASSWORD]&quot; Replace the values in angle brackets with your own values. Remember to save the password, as you will put that into a GitHub action secret. Note that you won’t get any confirmation in your terminal. You have to verify the existence of the file in Finder. Step 3: Validate the Final .p12 Before uploading to GitHub, make sure the final file works: 1 openssl pkcs12 -in fixed_cert.p12 -info -nodes This time, you should see: Your Apple Distribution identity Your certificate data Your private key data If you see all that — you’re good to go. Step 4: Base64 Encode for GitHub Actions Finally, we are able to encode the new .p12 file for GitHub: 1 base64 -i fixed_cert.p12 -o cert_base64.txt You can now paste the contents of cert_base64.txt into your DISTRIBUTION_CERT_P12 secret, and use the same password for DISTRIBUTION_CERT_P12_PW. If you put out the signing identity in your GitHub action again, you should see now something like this: 1 2 3 4 5 6 7 Run security find-identity -p codesigning -v 1) 0195BCD9574C7F93BFEDD19AFA640897 &quot;Apple Distribution: [YOUR (TEAM) NAME] (YOUR TEAM ID)&quot; 1 valid identities found [a-guid].mobileprovision [a-guid].mobileprovision [a-guid].mobileprovision [a-guid].mobileprovision Conclusion This process took more digging than it should have. I’m writing this down not only for my future self, but also to save others from burning time on cryptic OpenSSL errors and code signing quirks. If you’re setting up GitHub Actions for a .NET MAUI iOS project (or any iOS project, really), getting your certificate in shape is a foundational step. Hopefully, this post helps you get there faster. ⸻ Disclaimer: As stated already above, I had some help from ChatGPT to get to the root of the issue and shape this blog post. The final content is mine, but for the troubleshooting part, ChatGPT was an invaluable help. Additionally, the title image for this post was generated using AI.</content> </entry> <entry><title>Automating Apple Builds: A Practical Guide to GitHub Secrets</title><link href="https://msicc.net/automating-apple-builds-a-practical-guide-to-github-secrets/" rel="alternate" type="text/html" title="Automating Apple Builds: A Practical Guide to GitHub Secrets" /><published>2025-03-18T17:00:00+01:00</published> <updated>2025-11-14T15:50:04+01:00</updated> <id>https://msicc.net/automating-apple-builds-a-practical-guide-to-github-secrets/</id> <author> <name>msicc</name> </author> <category term="Dev Stories" /> <category term="iOS" /> <category term="macOS" /> <category term="MAUI" /> <category term="Automation" /> <summary>In this post, I show how to set up GitHub Secrets to securely manage credentials for automated Apple builds.</summary> <content type="text">Yesterday, I was listening to Merge Conflict Episode 454: Let’s ship some .NET iOS &amp; Android apps! CI/CD Deep Dive, where James and Frank discussed setting up CI/CD pipelines for .NET iOS and Android apps. Frank’s comments reminded me that I already have a GitHub Organization and that I could optimize my workflow by centralizing secrets instead of managing them separately in each repository. This led me to revisit my setup and document the process for others looking to streamline their Apple CI/CD pipeline. Organizing Secrets for Multiple Apps When managing multiple apps, you should consider creating a GitHub Organization to centralize secrets. This will allow you to share credentials across repositories while maintaining security and consistency. For a single repository: Navigate to Settings → Secrets and variables → Actions in your GitHub repository. For an organization-wide setup: Go to Organization Settings → Secrets and variables → Actions and define your secrets there. Please note that repository secrets will override organization secrets if they are named equally. GitHub will inform you if that happens, but only in the settings page. Configuring the keys for the App Store Connect API To authenticate with the App Store for tasks like uploading builds and managing TestFlight releases, we are going to need an App Store Connect API Key. Visit App Store Connect Integrations. Locate your Issuer ID and add it as a GitHub Secret: Secret name: APPSTORE_ISSUER_ID Find the Key ID of your API key and add it as a GitHub Secret: Secret name: APPSTORE_KEY_ID Retrieve your AuthKey_KeyId.p8 file. If you didn’t download it during creation, you’ll need to generate a new key. Open the .p8 file in a text editor and copy its contents. Create a new GitHub Secret: Secret name: APPSTORE_PRIVATE_KEY Encode the file in Base64 and store it as an additional secret: 1 cat AuthKey_KeyId.p8 | base64 | pbcopy Secret name: APPSTORE_PRIVATE_KEY_BASE64 By now, we should have the following four secrets added: APPSTORE_ISSUER_ID APPSTORE_KEY_ID APPSTORE_PRIVATE_KEY APPSTORE_PRIVATE_KEY_BASE64 Adding the Distribution Certificate To sign our iOS builds, we also need a Distribution Certificate stored as a .p12 file. Check if you already have the .p12 file for your current Distribution Profile. If you do, proceed with the steps below. If not, export a new .p12 file from the Keychain Access app on your Mac: Open the Keychain Access app. Locate your Distribution Certificate. Right-click and select Export [your certificate name]. Set a password and save the exported .p12 file. Store the .p12 file securely in GitHub Secrets: Encode the file in Base64 and add it as the content of the secret: 1 cat yourfile.p12 | base64 | pbcopy Secret name: DISTRIBUTION_CERT_P12 Create a new secret and store the password you used during the exportation of the .p12 file Secret name: DISTRIBUTION_CERT_P12_PW Now, we should have two additional secrets: DISTRIBUTION_CERT_P12 DISTRIBUTION_CERT_P12_PW Conclusion With these secrets in place, our GitHub Actions workflow will be able to securely authenticate with the App Store and sign our iOS builds without manually handling credentials. We’re now ready to integrate these secrets into our corresponding GitHub Action and automate our Apple deployment pipeline! By properly setting up GitHub Secrets, we enhanced the security of your CI/CD process while ensuring a smooth deployment experience. Stay tuned for a future post, where we’ll configure a complete GitHub Actions workflow for automated iOS builds including repositories our main app depends upon. Until the next post, happy coding, everyone! Disclaimer: This blog post was originally based on my rough notes, which were refined and structured with the help of AI to enhance readability and clarity. I then went through the whole post again making sure everything is correct and updated it where necessary. Additionally, the title image for this post was generated using AI.</content> </entry> <entry><title>5 Learning techniques you should know</title><link href="https://msicc.net/2025-03-03-Learning-techniques-you-should-know/" rel="alternate" type="text/html" title="5 Learning techniques you should know" /><published>2025-03-03T19:00:00+01:00</published> <updated>2026-07-21T06:35:32+02:00</updated> <id>https://msicc.net/2025-03-03-Learning-techniques-you-should-know/</id> <author> <name>msicc</name> </author> <category term="Editorials" /> <summary>In this blog post I will write about the (for me) most valuable techniques I learned while taking the Coursera course &quot;Learn how to learn&quot; from and with Dr. Barbara Oakley.</summary> <content type="text">When it comes to learning, I always had mixed results in the past. While I was able to memorize a lot of stuff for a short amount of time, I often had to look up several topics again and again once I hadn’t used them in a while. Additionally, I thought that I was now at an age where learning naturally gets harder (turns out, I was wrong!). Ultimately, I decided to take the Coursera online course “Learn How to Learn” as I am participating in a (paid) online class about iOS development, which will be crucial for my career growth. In this post, I will write about my top five learning techniques that I learned from the Coursera course and that I hope will help me get the most out of my online course on iOS development. Let’s dive in. Use Focus and Diffuse Modes of Your Brain The human brain has two modes: the focus mode, which allows us to work on highly complex topics—but just for a short amount of time. Every so often, you will reach a point where you can’t go any further (I already know this from programming). What helps in this case is to put your brain in diffuse mode, where it has only light activity, and your subconscious mind can take over and work on the problem on its own. For example, I go out for a walk when I can’t move forward with complex programming problems. More often than not, I find a solution during these walks. Another great time for this to happen is during a run or in the shower. Sometimes, even watching intellectually low-cost TV series can help (that’s what I love Star Trek: Lower Decks for!). Chunk Learning Topics Whenever You Can Another great technique is to break down your learning problems into smaller pieces. For this to be effective, you need to ensure you have a solid basic understanding of the topic you’re learning. A good way to achieve this is to watch a lecture and take notes. Once the lecture is over, try to divide your notes into small groups of information. If you have a hard time doing this, you should watch the lecture again and take more notes. You will improve over time, but in the beginning, you may have to rewatch lectures to get chunking right. Over time, you will be able to form small chunks and even groups of chunks (which are themselves chunks) that will help you better memorize the topics you are learning. The Good Old Pomodoro Technique As I already mentioned above, our brain is not able to focus for long periods. In fact, the opposite is true—our brain welcomes any distraction! When we give in to distractions, our brain rewards us with a cocktail of feel-good hormones. However, we can use this to our advantage—with the help of the Pomodoro Technique! This technique is as simple as it is genius, if you ask me. You set a timer for 25 minutes to work on a topic (or a chunk). Turn off all sources of distraction (like your smartphone notifications) and focus solely on your learning. Before you start, decide on a reward for yourself (for example, five minutes of social media or a coffee break). This will put your brain in focus mode, and you will stay motivated throughout your lesson (or even the entire day) as you earn small rewards between Pomodoro sessions. Self-Testing (Recalling) Our brain needs repeated exposure to new knowledge for it to be stored in long-term memory. Because of this, it is really important to test yourself often. Flashcards (or flashcard apps) can be extremely helpful. When creating flashcards, you are already testing yourself by recalling the information from memory. Of course, you should verify that your recall is accurate. Use every opportunity to go through your flashcards—even just five minutes a day will make a difference! Eat Your Frog First! What does that mean? It’s pretty simple — tackle your hardest lesson first (if possible). Use all the techniques mentioned above to get the most challenging task done first. This will keep you motivated throughout the day and help train your brain in applying the learning techniques (because you’ll need them to get through it!). That’s really all there is to it. Conclusion As you can see, we have a bunch of tools at our disposal to make learning easier. I am actually glad that I already use some of these techniques in my day job, so I am familiar with how they work. By applying them more consciously in the future, I am confident that I will be able to learn more effectively and sustainably than I have in the past. I hope some of you find these techniques as helpful as I do! Just one last thing: If you are serious about learning, I highly recommend taking the course on Coursera - it will be worth every minute you invest in it. Disclaimer: The title image is generated with AI (ChatGPT 4o)</content> </entry> <entry><title>How to profile a .NET MAUI iOS application on macOS</title><link href="https://msicc.net/2025-02-28-how-to-profile-net-maui-ios-app-on-macos/" rel="alternate" type="text/html" title="How to profile a .NET MAUI iOS application on macOS" /><published>2025-02-28T15:00:00+01:00</published> <updated>2025-11-14T15:50:04+01:00</updated> <id>https://msicc.net/2025-02-28-how-to-profile-net-maui-ios-app-on-macos/</id> <author> <name>msicc</name> </author> <category term="Dev Stories" /> <category term="iOS" /> <category term="MAUI" /> <summary>In this post, I am showing the steps needed to profile a .NET MAUI iOS application on macOS, utilizing the dotnet-trace global tool.</summary> <content type="text">In this post, I am showing the steps needed to profile a .NET MAUI iOS application on macOS. We will not use any IDE, just the terminal, utilizing the dotnet-trace global tool. While you can try to follow just the docs, here is how I got it running on several Macs. Install .NET global tools First, we need to install these .NET global tools: latest .NET SDK (https://dotnet.microsoft.com/en-us/download){:target=”_blank”} dotnet-trace: open a terminal and enter the following command (this will also update existing installations): 1 dotnet tool install --global dotnet-trace dotnet-dsrouter: enter the following command to enable the router for communication between macOS and iOS: 1 dotnet tool install --global dotnet-dsrouter Prepare the application In the .csproj file of your MAUI application, add the following property: 1 2 3 &lt;PropertyGroup Label=&quot;Enable Diagnostic Tools&quot; Condition=&quot;&#39;$(Configuration)|$(Platform)&#39;==&#39;Debug|AnyCPU&#39;&quot;&gt; &lt;EnableDiagnostics&gt;true&lt;/EnableDiagnostics&gt; &lt;/PropertyGroup&gt; Please note that you should not activate AOT or Trimming for the DEBUG configuration. Attach to the device To connect to your device, we need to start the dsrouter with the follwoing command: 1 dotnet-dsrouter server-client -ipcs ~/my-dev-port -tcpc 127.0.0.1:9001 --forward-port iOS This should result in something similar like this: Attention: Do never close this terminal window without properly ending the tool with CMD+C. You will block the Unix domain socket for further use, if you do. The only way to unblock the socket again is with a forced removal of the socket file (sudo rm ~/my-dev-port), which will remain as a zombie file on your mac otherwise. Leave this terminal window as is and open two additional terminal window (CMD + N). In one of the two, we are going to reinstall the app to make sure we have the DEBUG build on the device. If you do not know the device name, just run this command first: 1 xcrun xctrace list devices This will show you a list of all devices your mac currently knows. Select the one you’re testing on for the coming two occurences of the mlaunch command. Now that you know the name of your testing devices, enter the following command: 1 /usr/local/share/dotnet/packs/Microsoft.iOS.Sdk.net9.0_18.2/18.2.9170/tools/bin/mlaunch --installdev [PATH_TO_APP].app --devname [DEVICENAME] The paths may vary on your machine. If you are not seeing hidden folders in Finder (for the mlauch path), hit CMD+SHIFT+. on your keyboard to show them. Replace the file path to the .app file of your app and the device name as well. In the second terminal window, prepare the trace command, but do not yet start it (do not hit ENTER): 1 dotnet-trace collect --diagnostic-port ~/my-dev-port,connect --format speedscope; This will collect both a .nettrace file and also provides the trace in the speedscope format. Now go back to the terminal window where you installed your app. Enter the following command to start the app process and make it wait for the trace collector: 1 /usr/local/share/dotnet/packs/Microsoft.iOS.Sdk.net9.0_18.2/18.2.9170/tools/bin/mlaunch --launchdev [PATH_TO_APP].app --devname [DEVICENAME] --wait-for-exit --argument --connection-mode --argument none &#39;--setenv:DOTNET_DiagnosticPorts=127.0.0.1:9001,suspend,listen&#39; Once again, replace the file paths and the device name to match yours. Now you have to be fast and fire the prepared dotnet-trace command in the third terminal window. It should look like this: The app should run on your device. The second window will show the typical debug output. You can finish the process of tracing by closing the app or hitting CTRL+C on your keyboard. Once that happened, you will be presented with the file paths to both the .nettrace file and the speedscope.json file. Analyse the resulting trace files If you prefer the speedscope format, you can install this extension into VSCode for analyzing the file. For analysing the .nettrace file, just open the file in dotTrace (download here) as Snapshot. Analysing the .nettrace file is usually more helpful than the speedscope file. If you want to learn how to read the snaphot, the JetBrains documentation is a good starting point. As always, I hope this post will be helpful for some of you. Until the next post, happy coding, everyone! Disclaimer: the title image of this blog post is generated with AI (ChatGPT 4.o)</content> </entry> <entry><title>Lead Essentials: System Design and Networking Modules finished (White Belt 1st stripe)</title><link href="https://msicc.net/2025-02-25-leadessentials-system-design-and-networking-modules-finished/" rel="alternate" type="text/html" title="Lead Essentials: System Design and Networking Modules finished (White Belt 1st stripe)" /><published>2025-02-25T15:00:00+01:00</published> <updated>2026-07-21T06:35:32+02:00</updated> <id>https://msicc.net/2025-02-25-leadessentials-system-design-and-networking-modules-finished/</id> <author> <name>msicc</name> </author> <category term="Courses" /> <category term="LeadEssentials" /> <summary>I just completed the first two modules of iOS Lead Essentials, diving deep into system design principles, clean architecture, and advanced networking patterns in Swift.</summary> <content type="text">I recently started the iOS Lead Essentials course by Essential Developer. The program uses a belt system with stripes to reflect a developer’s progress—starting from no stripe and moving through increasingly advanced levels. After completing the first two modules, I received the first stripe on my White Belt, and I’d like to share what I’ve learned so far. What I Learned in the First Module: System Design The first module focused heavily on system design and app architecture. It gave me a much clearer understanding of how to approach building robust, maintainable applications from the ground up. I explored best practices for app architecture and learned how to analyze system requirements effectively. One of the most valuable takeaways was learning how to think, design, and communicate like a software architect — especially through diagramming techniques that clarify architectural decisions. We also tackled common pitfalls such as the misuse of singletons and global state, learned when they might be acceptable, when they’re dangerous, and how to avoid them using better alternatives. The module emphasized solid foundational principles, including the SOLID principles and how to apply them in Swift projects. It covered Clean Architecture in depth, along with modular design and Domain-Driven Design (DDD), all of which contribute to more scalable and testable codebases. Behavior-Driven Development (BDD) and use case analysis were also key parts of the training, helping to ensure that architecture remains aligned with user and business needs. I got hands-on experience in domain modeling and learned techniques to start implementing Swift apps even before backend services or design assets are finalized. What I Learned in the Second Module: Networking The second module went deep into networking in Swift. I revisited the fundamentals of the URL loading system, including URLSession, URLSessionDataTask, and even custom protocols using URLProtocol. These were then connected to real-world usage patterns, offering a pragmatic approach to building networking layers in production apps. A major focus was on creating testable networking layers. I worked through four different approaches to testing network requests—end-to-end, subclass-based, protocol-based mocking, and stubbing via URLProtocol. This helped me gain a better understanding of the trade-offs between them and how to choose the right one depending on context. The module also covered what many apps get wrong about reachability and how to approach it correctly. We looked at various ways to speed up development in Xcode, both by using frameworks efficiently and by reducing debugging time. From there, we shifted to Continuous Integration (CI) pipelines, learning how to automate builds and tests for improved reliability. Beyond networking, the module reinforced key concepts like object-oriented and functional programming in Swift, dependency injection, access control, and effective error handling. I also got to revisit important testing techniques - namely unit tests, integration tests and end-to-end tests. I also learned how to write them in a clean, maintainable way. Tools and strategies for refactoring, managing dependencies, and using version control with Git were also part of the curriculum. We explored Swift-specific techniques like closures and protocols as abstractions, writing safe code that avoids invalid states, and solving data races using Xcode’s diagnostics. I learned how to configure Xcode for test automation, gather and improve code coverage, randomize and parallelize test runs, and detect memory leaks automatically. The training also included best practices for writing tests, understanding and using different types of test doubles (spies, stubs, mocks, and fakes), handling async testing scenarios, and using Swift’s Result and error types effectively. As a bonus, we learned how to avoid common async bugs. What’s Next? I’ll continue progressing through the remaining belt levels in the Lead Essentials curriculum. The journey so far has already leveled up my thinking as a developer, and I’m looking forward to level up further as the course progresses. Click to see the full iOS Lead Essentials curriculum Disclaimer: This blog post was written with the help of AI, based on a bulleted summary of learning topics provided as part of the Lead Essentials program. The structured list served as the foundation for turning the content into a more readable, narrative-style post that reflects my personal learning experience. I reviewed and edited the post before publishing to ensure it meets the quality standards of this blog.</content> </entry> </feed>
