<?xml version="1.0" encoding="utf-8" standalone="no"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0">
  <channel><title>Mads Kristensen</title>
<description>A blog about everything technology.</description>
<generator>Miniblog.Core</generator>
<link>https://www.madskristensen.net/</link>
<language>en-us</language><item>
  <title>Solution Colors: Telling Visual Studio Windows Apart at a Glance</title>
  <link>https://www.madskristensen.net/blog/solution-colors-telling-visual-studio-windows-apart-at-a-glance/</link>
  <description>&lt;p&gt;When several Visual Studio instances are open, identifying the right one becomes a surprisingly repetitive task. Two windows showing different checkouts of the same solution can look almost identical. Reading the title works, but a small visual cue is easier to spot.&lt;/p&gt;

&lt;p&gt;That's the problem behind &lt;a href="https://marketplace.visualstudio.com/items?itemName=MadsKristensen.SolutionColors"&gt;Solution Colors&lt;/a&gt;. Inspired by the &lt;a href="https://marketplace.visualstudio.com/items?itemName=johnpapa.vscode-peacock"&gt;Peacock extension for VS Code&lt;/a&gt; and a &lt;a href="https://developercommunity.visualstudio.com/t/Per-SolutionFolder-Color-Theme/608136?space=8&amp;amp;ftype=idea"&gt;Visual Studio feature request&lt;/a&gt;, it associates a color with a solution or folder without replacing your editor theme.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://madskristensen.net/posts/files/solution-colors-windows_639246604550024077.png" alt="Overlapping Visual Studio windows with purple, cyan, green, and orange bottom borders." /&gt;&lt;/p&gt;

&lt;h2&gt;Give each solution a visual identity&lt;/h2&gt;

&lt;p&gt;Right-click the solution in Solution Explorer, choose &lt;strong&gt;Set Solution Color&lt;/strong&gt;, and select a color. The predefined choices use the familiar document-tab color palette, and &lt;strong&gt;Custom...&lt;/strong&gt; opens a color picker when you want something different. Open Folder workspaces are supported too.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://madskristensen.net/posts/files/solution-colors-context-menu_639246604552996906.png" alt="Solution Explorer context menu with Set Solution Color expanded and Gold highlighted." /&gt;&lt;/p&gt;

&lt;p&gt;The color can appear around the window, behind the solution name in the title bar, and in taskbar thumbnails. Taskbar icon overlays are also available, with the documented limitation that taskbar items need to be ungrouped. You can choose the surfaces that help you and leave the others alone.&lt;/p&gt;

&lt;p&gt;Under &lt;strong&gt;Tools &amp;gt; Options &amp;gt; Environment &amp;gt; Fonts and Colors &amp;gt; Solution Colors&lt;/strong&gt;, you can adjust border placement and thickness or enable automatic color assignment. Automatic mode selects from the palette using a hash of the solution path; explicitly saved colors take precedence.&lt;/p&gt;

&lt;h2&gt;A small extension with two kinds of integration&lt;/h2&gt;

&lt;p&gt;The project is an in-process VSIX built with the Visual Studio SDK and the Community Toolkit. Its &lt;code&gt;ToolkitPackage&lt;/code&gt; registers commands and listens for solution and folder open/close events. The menu lives in a VSCT command table, while the color commands share a generic &lt;code&gt;BaseCommand&amp;lt;T&amp;gt;&lt;/code&gt; implementation.&lt;/p&gt;

&lt;p&gt;That is the conventional part. Coloring the existing window chrome is less conventional.&lt;/p&gt;

&lt;p&gt;The implementation searches the WPF visual tree for named shell elements such as &lt;code&gt;BottomDockBorder&lt;/code&gt; and &lt;code&gt;PART_SolutionNameTextBlock&lt;/code&gt;. It changes brushes and border thickness rather than introducing a new tool window. For the title label, it also uses reflection to access the foreground property and Visual Studio's &lt;code&gt;ColorUtilities.CompareContrastWithBlackAndWhite&lt;/code&gt; to choose black or white text.&lt;/p&gt;

&lt;p&gt;This is an important tradeoff for extension authors: finding an element in the shell's visual tree is not the same as having a dedicated, stable extensibility contract for it. Names and structure can change. The lookup code is worth isolating, and these integrations need testing against the Visual Studio versions you support.&lt;/p&gt;

&lt;h2&gt;Decoration must not get in the way&lt;/h2&gt;

&lt;p&gt;One revealing detail is hit testing. The extension disables hit testing on the non-top borders it colors so they don't intercept mouse input. But it deliberately leaves the top element alone: &lt;code&gt;MainWindowTitleBar&lt;/code&gt; contains interactive UI, and disabling hit testing there would also block menu interaction.&lt;/p&gt;

&lt;p&gt;That distinction is easy to overlook when the feature appears to be "just a border." Decorative changes still participate in layout and input routing.&lt;/p&gt;

&lt;p&gt;Timing matters too. Package initialization does not guarantee that every shell element is ready. The startup path makes a bounded series of colorization attempts, with delays between them. UI changes switch to the main thread, while Git branch file reads are dispatched to a background task.&lt;/p&gt;

&lt;p&gt;Taskbar integration uses a different mechanism: WPF's &lt;code&gt;TaskbarItemInfo&lt;/code&gt;, noninteractive &lt;code&gt;ThumbButtonInfo&lt;/code&gt; entries, and an overlay image. There is no need to reach into the taskbar's visual tree.&lt;/p&gt;

&lt;h2&gt;Branch colors are a state problem, not just a brush problem&lt;/h2&gt;

&lt;p&gt;The settings offer one color across branches, a separate color per branch, or a combined gradient. Assignments are stored as simple &lt;code&gt;branch:color&lt;/code&gt; lines in a &lt;code&gt;color.txt&lt;/code&gt; file, normally under the solution's &lt;code&gt;.vs&lt;/code&gt; directory. An option puts the file in the root instead. The parser also accepts the older single-color format.&lt;/p&gt;

&lt;p&gt;There is a compatibility detail worth knowing: the implementation uses the literal key &lt;code&gt;master&lt;/code&gt; for its shared/base color. That is an internal convention, not automatic discovery of a repository's configured default branch.&lt;/p&gt;

&lt;p&gt;A recent fix illustrates why separating decisions from rendering helps. In per-branch mode, a branch with an explicit color should remain colored even when no base color has been assigned. The code now expresses the removal decision in &lt;code&gt;ShouldRemoveColorization&lt;/code&gt;, with focused unit tests covering the different modes and automatic-color behavior.&lt;/p&gt;

&lt;p&gt;That is a useful pattern beyond this extension: put the state rules in a testable method, then let the UI code apply the result. You shouldn't need to launch an experimental Visual Studio instance just to verify whether a missing base setting overrides a branch-specific one.&lt;/p&gt;

&lt;h2&gt;Try it, or borrow the ideas&lt;/h2&gt;

&lt;p&gt;If you regularly juggle Visual Studio windows, &lt;a href="https://marketplace.visualstudio.com/items?itemName=MadsKristensen.SolutionColors"&gt;install Solution Colors from the Marketplace&lt;/a&gt; and give your solutions a visual identity.&lt;/p&gt;

&lt;p&gt;If you're building extensions, &lt;a href="https://github.com/madskristensen/SolutionColors"&gt;browse the source on GitHub&lt;/a&gt;. Start with &lt;code&gt;SolutionColorsPackage&lt;/code&gt; for the lifecycle, &lt;code&gt;ColorHelper&lt;/code&gt; for the shell integration, and the tests for the branch-color rules. A few pixels of color turn out to be a useful case study in extending the IDE without getting in the user's way.&lt;/p&gt;</description>
  <author>test@example.com</author>
  <category>visual studio</category>
  <category>extensions</category>
  <guid isPermaLink="false">https://www.madskristensen.net/blog/solution-colors-telling-visual-studio-windows-apart-at-a-glance/</guid>
  <pubDate>Thu, 10 Sep 2026 18:08:54 GMT</pubDate>
</item>
<item>
  <title>Lore for Visual Studio: Source control without leaving the IDE</title>
  <link>https://www.madskristensen.net/blog/lore-for-visual-studio-source-control-without-leaving-the-ide/</link>
  <description>&lt;p&gt;&lt;a href="https://github.com/EpicGames/lore"&gt;Lore&lt;/a&gt; is a new source control system from Epic Games. It is designed for fast, distributed workflows, with local repositories that work offline and optional Lore servers when you need to collaborate.&lt;/p&gt;

&lt;p&gt;Lore for Visual Studio brings that workflow directly into the IDE. It is also a practical example of what it takes to add a new source-control provider to Visual Studio: integrate onboarding, status, changes, commits, history, branches, authentication, and native diff and merge experiences into the places developers already use.&lt;/p&gt;

&lt;p&gt;The extension lets you onboard a solution or folder, see file status in Solution Explorer, review changes, commit, manage branches, inspect history, and synchronize with a Lore server - all without dropping to a terminal.&lt;/p&gt;

&lt;h2&gt;A source-control provider that feels native&lt;/h2&gt;

&lt;p&gt;Visual Studio already provides the integration points a source-control provider needs to participate in the everyday development workflow. Lore for Visual Studio uses them to surface repository status in Solution Explorer, provide commands in solution and file context menus, and offer dedicated Changes and History tool windows. Rather than making developers learn a separate workflow, the extension makes Lore available alongside the IDE experiences they already know.&lt;/p&gt;

&lt;h2&gt;Start with a local repository&lt;/h2&gt;

&lt;p&gt;Open a solution or folder and select &lt;strong&gt;Add to Source Control&lt;/strong&gt; in the Visual Studio status bar. Choose &lt;strong&gt;Lore&lt;/strong&gt;, then create a local repository.&lt;/p&gt;

&lt;p&gt;Local repositories are fully offline. You can commit, create branches, switch branches, and browse history while all content remains on your machine. When you are ready to collaborate, create or connect to a Lore server and push your work.&lt;/p&gt;

&lt;p&gt;&lt;img src="/posts/files/lorevs-add-to-source-control_639235472182075744.png" alt="Add a solution to Lore" /&gt;&lt;/p&gt;

&lt;p&gt;You can also clone an existing Lore repository directly from Solution Explorer. The extension checks that the server is reachable, clones the working tree, and records the remote configuration so push and pull are ready immediately.&lt;/p&gt;

&lt;p&gt;&lt;img src="/posts/files/lorevs-clone-repository_639235472185109683.png" alt="Clone a Lore repository" /&gt;&lt;/p&gt;

&lt;h2&gt;Source-control status where you expect it&lt;/h2&gt;

&lt;p&gt;Once a solution is under Lore source control, status glyphs appear beside files in Solution Explorer. They refresh automatically as documents are saved, and the Lore Changes window provides a manual refresh command whenever you need one.&lt;/p&gt;

&lt;p&gt;File context menus include the essentials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Undo Changes&lt;/strong&gt; to restore the committed version&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compare with Unmodified&lt;/strong&gt; to view a native Visual Studio diff&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ignore and Untrack Item&lt;/strong&gt; to add a file to &lt;code&gt;.loreignore&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Moves and renames performed in Solution Explorer are tracked automatically. The extension also uses Visual Studio's shared file-change service to detect changes made outside the IDE.&lt;/p&gt;

&lt;h2&gt;Review and commit changes&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Lore Changes&lt;/strong&gt; window provides a dedicated commit experience modeled after Visual Studio's Git Changes window.&lt;/p&gt;

&lt;p&gt;&lt;img src="/posts/files/lorevs-changes-window_639235472178571660.png" alt="Lore Changes window" /&gt;&lt;/p&gt;

&lt;p&gt;It presents changed files in a repository-root folder tree, including status badges for modified, added, deleted, renamed, and copied files.&lt;/p&gt;

&lt;p&gt;You can select exactly which files belong in the next commit. Folder checkboxes are tri-state, the header checkbox selects everything at once, and your selection survives automatic refreshes while you write a commit message.&lt;/p&gt;

&lt;p&gt;From the window, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commit selected files or commit and push in one operation&lt;/li&gt;
&lt;li&gt;Amend the latest revision&lt;/li&gt;
&lt;li&gt;Open native Visual Studio diffs for individual files&lt;/li&gt;
&lt;li&gt;Discard a file's changes&lt;/li&gt;
&lt;li&gt;Pull, push, and refresh from the toolbar&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Browse history without touching the working tree&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Lore History&lt;/strong&gt; tool window shows a read-only log for the repository associated with the open solution or folder.&lt;/p&gt;

&lt;p&gt;History is paged, newest first, so even repositories with thousands of revisions remain responsive. Each revision shows its message, short hash, author, and local commit time. Select a revision to load only the files that it changed, then double-click a file to compare it with its direct parent in Visual Studio's native diff viewer.&lt;/p&gt;

&lt;p&gt;The window stays in sync with the workspace and refreshes automatically after relevant Lore operations, such as commits, pulls, branch changes, and merges.&lt;/p&gt;

&lt;h2&gt;Branching and merging&lt;/h2&gt;

&lt;p&gt;The branch picker in the Lore Changes window lets you switch branches, create a branch from the current revision, and merge another branch into the one you are working on.&lt;/p&gt;

&lt;p&gt;Lore is offline-first. When the needed content is already cached locally, switching and merging do not require a network round trip. If content has been evicted from the local cache, Lore fetches it from the configured server and clearly reports when the server is unavailable.&lt;/p&gt;

&lt;p&gt;When a merge conflicts, Visual Studio's built-in three-way merge experience opens for each conflicted file. Resolve the conflicts, accept the results, and Lore completes the merge commit.&lt;/p&gt;

&lt;h2&gt;Sign in only when it matters&lt;/h2&gt;

&lt;p&gt;There is no separate sign-in command. If a server-backed operation such as cloning, pushing, or pulling requires authentication, Lore asks whether to sign in and opens the default browser. After sign-in completes, the original operation is retried.&lt;/p&gt;

&lt;p&gt;That keeps the experience simple: work locally when you can, authenticate only when a server operation actually needs it.&lt;/p&gt;

&lt;h2&gt;Try it out&lt;/h2&gt;

&lt;p&gt;Lore for Visual Studio is available from the &lt;a href="https://marketplace.visualstudio.com/items?itemName=MadsKristensen.LoreVS"&gt;Visual Studio Marketplace&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The extension is open source, and contributions are welcome on &lt;a href="https://github.com/madskristensen/LoreVS"&gt;GitHub&lt;/a&gt;. If you are building a source-control integration of your own, the project is also an example of how a provider can fit into Visual Studio's existing source-control workflow.&lt;/p&gt;</description>
  <author>test@example.com</author>
  <category>visual studio</category>
  <category>extensions</category>
  <category>source control</category>
  <guid isPermaLink="false">https://www.madskristensen.net/blog/lore-for-visual-studio-source-control-without-leaving-the-ide/</guid>
  <pubDate>Fri, 28 Aug 2026 20:54:50 GMT</pubDate>
</item>
<item>
  <title>Building Max Line Length: Making EditorConfig Formatting Visible in Visual Studio</title>
  <link>https://www.madskristensen.net/blog/building-max-line-length-making-editorconfig-formatting-visible-in-visual-studio/</link>
  <description>&lt;p&gt;Line length is one of those conventions that teams agree on easily and enforce inconsistently. EditorConfig gives us a shared vocabulary with &lt;code&gt;max_line_length&lt;/code&gt;, but Visual Studio does not natively draw a boundary for it or apply it during formatting.&lt;/p&gt;

&lt;p&gt;That gap is what inspired &lt;a href="https://github.com/madskristensen/MaxLineLength"&gt;Max Line Length&lt;/a&gt;. The extension makes the effective EditorConfig value visible in the editor and adds a carefully constrained reflow pipeline for formatting and Code Cleanup.&lt;/p&gt;

&lt;h2&gt;Starting with the effective EditorConfig value&lt;/h2&gt;
&lt;p&gt;The extension does not introduce a second configuration system. A repository can declare its convention as usual:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[*.{cs,vb}]
max_line_length = 120&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For each text view, Max Line Length resolves the effective value for the document, including nested configuration files and matching sections. If the value is missing, invalid, or set to &lt;code&gt;unset&lt;/code&gt;, the ruler is removed.&lt;/p&gt;
&lt;p&gt;The interesting part is keeping the editor responsive when configuration changes. The adornment listens for refresh notifications, resolves file-based settings away from the UI thread, then switches back to the main thread before updating the view. A version counter prevents stale asynchronous results from replacing newer configuration values.&lt;/p&gt;

&lt;h2&gt;Drawing a ruler without interfering with editing&lt;/h2&gt;
&lt;p&gt;The ruler is implemented as a Visual Studio editor adornment. The extension obtains an &lt;code&gt;IWpfTextView&lt;/code&gt;, creates an &lt;code&gt;IAdornmentLayer&lt;/code&gt;, and owns a WPF &lt;code&gt;Line&lt;/code&gt; element. Its horizontal position is calculated from the formatted line source, indentation, column width, and the resolved maximum length.&lt;/p&gt;
&lt;p&gt;The adornment follows layout changes, so it tracks scrolling, zoom, font changes, and viewport updates. It is not hit-testable and does not intercept mouse or keyboard input. The extension also checks for Visual Studio's diff view role and does not display the ruler there.&lt;/p&gt;
&lt;p&gt;Color customization uses Visual Studio's format map rather than a private option. The &lt;strong&gt;Max Line Length ruler&lt;/strong&gt; format is exposed through Fonts and Colors, and the adornment reads the configured foreground brush when it redraws.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://madskristensen.net/posts/files/max-line-length-ruler_639227523019405959.png" alt="Visual Studio editor showing the Max Line Length vertical ruler."&gt;&lt;/p&gt;

&lt;h2&gt;One reflow service for several editor commands&lt;/h2&gt;
&lt;p&gt;Formatting support began as command-specific logic, but that quickly creates inconsistencies. The extension now centralizes the decision in &lt;code&gt;DocumentReflowService&lt;/code&gt;. It determines whether a content type can be reflowed, selects the appropriate engine, and applies the resulting text changes.&lt;/p&gt;
&lt;p&gt;Visual Studio's MEF composition supplies the classifier service used for comment-aware languages. For C# and Visual Basic, the extension uses language-specific reflow engines for lists and expressions. For classified-comment content types such as JavaScript, TypeScript, C++, CSS, LESS, SCSS, and SQL, it asks the editor classifier for comment spans and limits changes to those spans.&lt;/p&gt;
&lt;p&gt;Markdown and plain text use separate engines so that structural constructs remain intact. Markdown headings, tables, lists, block quotes, code fences, inline code, HTML, front matter, and hard line breaks are protected instead of being treated as ordinary prose.&lt;/p&gt;

&lt;h2&gt;Being conservative is a feature&lt;/h2&gt;
&lt;p&gt;Reflow changes source text, so the extension intentionally avoids transformations that could alter meaning. C# and Visual Basic string and character literals are left alone. Structured XML documentation elements, examples, lists, and preformatted content are preserved while ordinary documentation prose can be wrapped.&lt;/p&gt;
&lt;p&gt;Formatter suppression markers provide an explicit escape hatch. The extension recognizes &lt;code&gt;@formatter:off&lt;/code&gt; and &lt;code&gt;@formatter:on&lt;/code&gt; in the relevant comment syntax and excludes the protected region from reflow.&lt;/p&gt;
&lt;p&gt;Selection formatting is similarly constrained. It operates on complete selected lines or complete C# and Visual Basic syntax constructs rather than making partial edits that could produce surprising results.&lt;/p&gt;

&lt;h2&gt;Adding Code Cleanup without changing existing profiles&lt;/h2&gt;
&lt;p&gt;The Code Cleanup integration is opt-in. The fixer is exported through Visual Studio's &lt;code&gt;ICodeCleanUpFixerProvider&lt;/code&gt; and exposes a localized fix ID named &lt;strong&gt;Reflow lines to configured maximum length&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;When enabled in a cleanup profile, the fixer obtains the document path, resolves the same &lt;code&gt;ReflowOptions&lt;/code&gt; used by the formatting commands, and sends the buffer through &lt;code&gt;DocumentReflowService&lt;/code&gt;. This keeps document cleanup and manual formatting consistent.&lt;/p&gt;
&lt;p&gt;The fixer is disabled unless the user explicitly adds it to a profile. That default matters: installing an extension should not silently change existing cleanup behavior. The integration supports document cleanup and cleanup on save, while project and solution cleanup are not intercepted.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://madskristensen.net/posts/files/max-line-length-reflow_639227523039371958.gif" alt="Visual Studio reflowing a document to the configured maximum line length."&gt;&lt;/p&gt;

&lt;h2&gt;Lessons for other Visual Studio extensions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use the editor's effective configuration instead of creating a parallel options system.&lt;/li&gt;
&lt;li&gt;Keep UI adornments lightweight and recalculate them from the current formatted line source.&lt;/li&gt;
&lt;li&gt;Centralize behavior shared by commands, cleanup, and future entry points.&lt;/li&gt;
&lt;li&gt;Use classifications and syntax-aware boundaries to avoid editing executable code accidentally.&lt;/li&gt;
&lt;li&gt;Make automated cleanup opt-in when it can change source text.&lt;/li&gt;
&lt;li&gt;Design asynchronous refresh paths so stale results cannot overwrite newer state.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Try it or inspect the implementation&lt;/h2&gt;
&lt;p&gt;Install &lt;a href="https://marketplace.visualstudio.com/items?itemName=MadsKristensen.MaxLineLength"&gt;Max Line Length from the Visual Studio Marketplace&lt;/a&gt;, or download the latest CI build from the &lt;a href="https://www.vsixgallery.com/extension/MaxLineLength.bbc727b4-1dfb-4d9e-a159-032529f396ed"&gt;Open VSIX Gallery&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The complete source is available on &lt;a href="https://github.com/madskristensen/MaxLineLength"&gt;GitHub&lt;/a&gt;. The project is a useful example of combining editor adornments, EditorConfig resolution, MEF services, content classifiers, language-aware text transformations, and Code Cleanup integration in a focused Visual Studio extension.&lt;/p&gt;</description>
  <author>test@example.com</author>
  <category>visual studio</category>
  <category>extensions</category>
  <category>editorconfig</category>
  <guid isPermaLink="false">https://www.madskristensen.net/blog/building-max-line-length-making-editorconfig-formatting-visible-in-visual-studio/</guid>
  <pubDate>Wed, 19 Aug 2026 16:05:17 GMT</pubDate>
</item>
<item>
  <title>Building Bookmark Studio: bookmarks that belong to the codebase</title>
  <link>https://www.madskristensen.net/blog/building-bookmark-studio-bookmarks-that-belong-to-the-codebase/</link>
  <description>&lt;p&gt;Visual Studio bookmarks are useful for marking a line and returning to it later. The problem is that a line number alone does not say why the location matters, how it relates to other locations, or whether the rest of the team should know about it.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BookmarkStudio"&gt;Bookmark Studio&lt;/a&gt; treats a bookmark as a small piece of project knowledge. A bookmark can have a name, note, color, folder, and one of nine direct-navigation shortcuts. It can stay private in the &lt;code&gt;.vs&lt;/code&gt; folder, live at the workspace level in source control, or be global across solutions.&lt;/p&gt;
&lt;p&gt;That sounds like a straightforward feature, but it touches several distinct parts of Visual Studio extensibility: editor tags and glyphs, text-buffer tracking, commands, a searchable tool window, options, solution lifecycle events, and persistent storage. The interesting part is making those pieces behave like one feature rather than a collection of extension points.&lt;/p&gt;
&lt;p&gt;&lt;img src="/posts/files/bookmark-studio-manager_639220100628782353.png" alt="Bookmark Manager tool window showing color-coded bookmarks organized in folders." /&gt;&lt;/p&gt;
&lt;h2&gt;Keep the editor layer fast&lt;/h2&gt;
&lt;p&gt;The visible bookmark in the editor margin starts with a MEF-exported &lt;code&gt;IViewTaggerProvider&lt;/code&gt;. It creates an &lt;code&gt;ITagger&amp;lt;BookmarkGlyphTag&amp;gt;&lt;/code&gt; for primary text document views, while an &lt;code&gt;IGlyphFactory&lt;/code&gt; turns each tag into the colored square shown in the glyph margin. If a bookmark has a shortcut from 1 through 9, the number is drawn inside the glyph.&lt;/p&gt;
&lt;p&gt;The tag also carries the bookmark label, note, color, and shortcut number. That gives the glyph factory enough information to build a useful tooltip and context menu without reading the bookmark file. A second tagger produces &lt;code&gt;OverviewMarkTag&lt;/code&gt; instances so the same bookmarks appear as colored markers in the editor scrollbar.&lt;/p&gt;
&lt;p&gt;A useful rule for editor extensions is to keep &lt;code&gt;GetTags&lt;/code&gt; cheap. Visual Studio can ask for tags often, including while scrolling and editing. Bookmark Studio does not load JSON or filter the complete bookmark collection on every request. The shared session owns the current bookmark state, and each tagger caches only the bookmarks for its document. When the session reports a change, the tagger replaces that small cache and raises &lt;code&gt;TagsChanged&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That separation is broadly useful: do I/O and normalization in a service, then give the editor a read-optimized snapshot.&lt;/p&gt;
&lt;h2&gt;A persisted line number is not an editor position&lt;/h2&gt;
&lt;p&gt;A bookmark must be serializable, so its stored location includes a document path and line number. An open text buffer is more dynamic. Insert three lines above a bookmark and the persisted number is immediately stale.&lt;/p&gt;
&lt;p&gt;Bookmark Studio handles that mismatch with &lt;code&gt;ITrackingPoint&lt;/code&gt;. While a document is open, each bookmark gets a tracking point anchored inside the line content. The editor moves that point as the buffer changes, which keeps the glyph attached to the intended code instead of the original numeric line. The tagger can still fall back to the stored line number when no tracking point exists.&lt;/p&gt;
&lt;p&gt;There is an additional wrinkle: users can explicitly drag a glyph to another line. The tagger therefore distinguishes an editor-tracked move from an explicit bookmark move before deciding whether to preserve or rebuild the tracking point. The custom &lt;code&gt;MouseProcessorBase&lt;/code&gt; implementation calculates the target editor line, displays a drop indicator, updates the bookmark, and refreshes the manager if it is open.&lt;/p&gt;
&lt;p&gt;&lt;img src="/posts/files/bookmark-studio-drag-drop_639220100634749316.gif" alt="Dragging a numbered bookmark glyph to a different line in the Visual Studio editor." /&gt;&lt;/p&gt;
&lt;p&gt;The lesson is that storage coordinates and live editor coordinates are different concepts. Persist a stable representation, but use the text model's tracking primitives while a buffer is active.&lt;/p&gt;
&lt;h2&gt;Reuse Visual Studio instead of recreating it&lt;/h2&gt;
&lt;p&gt;The Bookmark Manager is a WPF control hosted in a &lt;code&gt;BaseToolWindow&lt;/code&gt;. Its surrounding experience comes from Visual Studio rather than custom imitations.&lt;/p&gt;
&lt;p&gt;The package registers the window with &lt;code&gt;ProvideToolWindow&lt;/code&gt;, docks it alongside Solution Explorer, and assigns a toolbar declared through VSCT to &lt;code&gt;ToolWindowPane.ToolBar&lt;/code&gt;. Search uses the tool window's native search box by enabling &lt;code&gt;SearchEnabled&lt;/code&gt; and implementing &lt;code&gt;IVsSearchTask&lt;/code&gt;. Commands and keyboard bindings are also declared through VSCT, including direct navigation with &lt;code&gt;Alt+Shift+1&lt;/code&gt; through &lt;code&gt;Alt+Shift+9&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This approach matters for more than appearance. Native command placement participates in Visual Studio's command routing, toolbar customization, keyboard system, and accessibility behavior. The same principle applies to the search box: using the host's search contract gives the tool window familiar behavior without building another search control.&lt;/p&gt;
&lt;p&gt;Bookmark Studio also offers an opt-in bridge from Visual Studio's built-in bookmark gestures. &lt;code&gt;VS.Commands.InterceptAsync&lt;/code&gt; handles commands such as toggle, next, previous, and clear-in-document. The first toggle can prompt the user, and the setting determines whether later commands stop in Bookmark Studio or continue to Visual Studio's native implementation. The extension adds its own direct gestures as well, so interception is a choice rather than a requirement.&lt;/p&gt;
&lt;p&gt;&lt;img src="/posts/files/bookmark-studio-toolbar_639220100625484895.png" alt="Visual Studio toolbar menu listing numbered bookmarks for quick navigation." /&gt;&lt;/p&gt;
&lt;h2&gt;Make sharing explicit in the storage model&lt;/h2&gt;
&lt;p&gt;Bookmarks are stored with &lt;code&gt;System.Text.Json&lt;/code&gt; in &lt;code&gt;.bookmarks.json&lt;/code&gt;. The file is a folder tree containing bookmark IDs, relative document paths, line numbers, labels, notes, colors, shortcut slots, and manual sort indexes.&lt;/p&gt;
&lt;p&gt;There are three storage scopes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Personal bookmarks live under the solution's &lt;code&gt;.vs&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;Workspace bookmarks live beside the solution or at the Git repository root and can be committed.&lt;/li&gt;
&lt;li&gt;Global bookmarks live in &lt;code&gt;%USERPROFILE%\.bookmarks.json&lt;/code&gt; and use absolute paths because they are machine-specific.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Workspace discovery walks upward from the solution directory and reuses the first bookmarks file it finds. This is important for repositories where the solution sits below the root. New workspace files record &lt;code&gt;&amp;quot;documentPathRoot&amp;quot;: &amp;quot;bookmarksFile&amp;quot;&lt;/code&gt;, which tells the loader to resolve relative document paths from the bookmarks file itself. Existing files without that marker retain the older solution-relative behavior.&lt;/p&gt;
&lt;p&gt;That small format marker avoids a common portability bug. A relative path is only meaningful when both writer and reader agree on its base directory. If a team-shared file may move higher in a repository, the base must travel with the format or be defined by it.&lt;/p&gt;
&lt;h2&gt;Naming can use the editor's understanding of code&lt;/h2&gt;
&lt;p&gt;When name prompting is enabled, Bookmark Studio does not have to settle for the current line as the label. A MEF service queries Visual Studio's classification system and ranks identifiers such as methods, properties, fields, and types. It supports view classifiers first, then projection buffers and tag aggregators as fallbacks.&lt;/p&gt;
&lt;p&gt;The complete fallback order is pragmatic: selected text, classified identifier, word under the caret, file name, trimmed line text, and finally &lt;code&gt;Bookmark&lt;/code&gt;. Duplicate labels receive a numeric suffix.&lt;/p&gt;
&lt;p&gt;This is a good middle ground for editor tooling. The extension gets language-aware suggestions from classifications that Visual Studio already produces, without taking a dependency on a specific language compiler or syntax tree.&lt;/p&gt;
&lt;h2&gt;The extension points form one state machine&lt;/h2&gt;
&lt;p&gt;The package loads in the background for solutions, Open Folder workspaces, and the no-solution context because global bookmarks remain useful without a solution. Solution and folder events invalidate the cached path and refresh the shared session. Editor taggers, commands, and the tool window all observe or update that same session.&lt;/p&gt;
&lt;p&gt;That shared state is the architectural center of Bookmark Studio. The editor should not own persistence, the tool window should not be the only source of truth, and commands should not need to know where JSON lives. Each surface delegates bookmark operations to services, then reacts to the resulting state change.&lt;/p&gt;
&lt;p&gt;For extension authors, the reusable ideas are straightforward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Keep taggers read-only and inexpensive.&lt;/li&gt;
&lt;li&gt;Use tracking points for live buffers instead of treating line numbers as stable positions.&lt;/li&gt;
&lt;li&gt;Prefer native tool window search, VSCT commands, and host chrome over look-alike WPF controls.&lt;/li&gt;
&lt;li&gt;Define the base of relative paths as part of a shareable file format.&lt;/li&gt;
&lt;li&gt;Put cross-surface state behind a service so commands, editors, and tool windows stay synchronized.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Bookmark Studio is available on the &lt;a href="https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BookmarkStudio"&gt;Visual Studio Marketplace&lt;/a&gt;. The complete source, including the editor providers, tool window, command table, storage format, and tests, is on &lt;a href="https://github.com/madskristensen/BookmarkStudio"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;</description>
  <author>test@example.com</author>
  <category>visual studio</category>
  <category>vsix</category>
  <guid isPermaLink="false">https://www.madskristensen.net/blog/building-bookmark-studio-bookmarks-that-belong-to-the-codebase/</guid>
  <pubDate>Tue, 11 Aug 2026 01:54:47 GMT</pubDate>
</item>
<item>
  <title>Building CodeShot: turning Visual Studio editor state into a shareable PNG</title>
  <link>https://www.madskristensen.net/blog/building-codeshot-turning-visual-studio-editor-state-into-a-shareable-png/</link>
  <description>&lt;p&gt;Turning a code sample into an image sounds simple until the image needs to look like the code inside Visual Studio.&lt;/p&gt;

&lt;p&gt;Copying text into a design tool loses editor colors and formatting. Taking a normal screen capture includes tabs, margins, toolbars, and whatever else happens to be visible. It also leaves no convenient way to emphasize an expression or cover a value before sharing the image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=madskristensen.CodeShot"&gt;CodeShot&lt;/a&gt; addresses that workflow inside Visual Studio. It takes inspiration from CodeSnap, but its implementation is shaped by the Visual Studio editor and shell.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src="/posts/files/codeshot-workflow_639217263672831843.gif" alt="Creating and resizing a code screenshot in Visual Studio"&gt;
  &lt;figcaption&gt;Select code, open CodeShot, adjust the result, and copy or save the PNG without leaving Visual Studio.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2&gt;Rebuilding the editor instead of photographing it&lt;/h2&gt;

&lt;p&gt;The basic workflow starts with an editor selection. CodeShot opens a tool window, creates a preview using the current syntax colors, and immediately copies the initial image. The preview can add line numbers, a title bar, backgrounds, padding, shadows, and a configurable export scale.&lt;/p&gt;

&lt;p&gt;The important implementation detail is that the editor screenshot is not a bitmap of the editor. CodeShot reconstructs the selected text as WPF content.&lt;/p&gt;

&lt;p&gt;It obtains the active &lt;code&gt;IVsTextView&lt;/code&gt; through &lt;code&gt;IVsTextManager&lt;/code&gt;, converts it to an &lt;code&gt;IWpfTextView&lt;/code&gt; with &lt;code&gt;IVsEditorAdaptersFactoryService&lt;/code&gt;, and reads the selected &lt;code&gt;SnapshotSpan&lt;/code&gt; values. Common indentation can then be removed without changing the underlying document.&lt;/p&gt;

&lt;p&gt;Syntax colors come from two MEF services:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;IViewClassifierAggregatorService&lt;/code&gt; supplies the classification spans.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;IClassificationFormatMapService&lt;/code&gt; maps each classification type to the colors currently used by the editor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rather than asking the classifier about every line, CodeShot classifies the enclosing selection once. It then walks the ordered spans and slices them into WPF &lt;code&gt;Run&lt;/code&gt; elements for each selected line. Unclassified gaps use the editor's default foreground, while classifications can contribute their own foreground and background brushes.&lt;/p&gt;

&lt;p&gt;This approach has two useful properties. The result follows the active Visual Studio color settings, and the capture surface can be laid out independently of the editor viewport. Long selections are not clipped just because the tool window is small.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src="/posts/files/codeshot-tool-window_639217263648067002.png" alt="CodeShot tool window displaying a syntax-colored code screenshot"&gt;
  &lt;figcaption&gt;The preview is reconstructed from editor text and classifications rather than cropped from the screen.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2&gt;Rendering at an explicit scale&lt;/h2&gt;

&lt;p&gt;The completed WPF surface is rendered through a &lt;code&gt;VisualBrush&lt;/code&gt; into a &lt;code&gt;RenderTargetBitmap&lt;/code&gt;. Export scale is explicit instead of being derived from monitor DPI. That makes the same selection produce the same pixel dimensions on different displays.&lt;/p&gt;

&lt;p&gt;There are practical limits around this step. CodeShot rejects dimensions above 16,384 pixels and caps the total pixel count before allocating the render target. The resulting &lt;code&gt;BitmapSource&lt;/code&gt; is frozen before it crosses thread boundaries.&lt;/p&gt;

&lt;p&gt;The clipboard also needs more care than a call to &lt;code&gt;Clipboard.SetImage&lt;/code&gt;. Some paste targets discard alpha from the normal WPF bitmap format, so CodeShot places both the compatibility bitmap and an encoded PNG on the clipboard. The clipboard data is copied rather than referenced, allowing the image to remain available after Visual Studio closes.&lt;/p&gt;

&lt;p&gt;There is an optional plain-text clipboard representation too. That keeps code searchable when an application prefers text over images. If the screenshot contains a redaction, CodeShot deliberately omits the original text so the clipboard cannot reveal what the PNG hides.&lt;/p&gt;

&lt;h2&gt;Annotations are part of the capture surface&lt;/h2&gt;

&lt;p&gt;Annotations are ordinary WPF elements layered over the preview. Rectangles, arrows, expression highlights, text notes, and opaque redactions share a controller responsible for drawing, selection, moving, resizing, and erasing.&lt;/p&gt;

&lt;p&gt;Crop operations and annotations use the same bounded edit-history model. This keeps undo and redo predictable while preventing an editing session from retaining an unlimited number of image and geometry states.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src="/posts/files/codeshot-annotations_639217263647484300.png" alt="CodeShot preview with annotations highlighting important code"&gt;
  &lt;figcaption&gt;Annotations are rendered into the exported image, with redactions also changing clipboard behavior.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2&gt;Capturing a tool window is a different problem&lt;/h2&gt;

&lt;p&gt;Text reconstruction works well for editor selections, but it cannot capture Error List, Test Explorer, Solution Explorer, or a tool window containing Win32 or WebView content. Those surfaces have to be captured from the pixels currently displayed.&lt;/p&gt;

&lt;p&gt;CodeShot adds a command to Visual Studio's docked-window context menu through VSCT. When invoked, it uses &lt;code&gt;IVsMonitorSelection&lt;/code&gt; to find the current &lt;code&gt;IVsWindowFrame&lt;/code&gt; and &lt;code&gt;IVsWindowFrame4.GetWindowScreenRect&lt;/code&gt; to obtain its screen coordinates.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src="/posts/files/codeshot-tool-window-context-menu_639217263646610319.png" alt="Take Screenshot command in a Visual Studio tool window context menu"&gt;
  &lt;figcaption&gt;The command is attached to Visual Studio's native docked-window context menu.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Finding the content rectangle is only the beginning. A docked group may contain sibling tabs, rounded shell chrome, transparent corners, and content rendered outside the WPF visual tree.&lt;/p&gt;

&lt;p&gt;CodeShot combines several mechanisms:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;UI Automation locates the selected tab and its docking container.&lt;/li&gt;
  &lt;li&gt;The WPF visual tree supplies a mask for the rounded Visual Studio frame.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;Graphics.CopyFromScreen&lt;/code&gt; captures the actual visible pixels, including Win32 and WebView content.&lt;/li&gt;
  &lt;li&gt;The mask removes sibling tabs and leaves pixels outside the rounded frame transparent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because UI Automation runs away from the UI thread, the target can change while its geometry is being inspected. Before copying pixels, CodeShot verifies that the selected COM frame, bounds, root window, and visibility still match the original target. Captures are serialized with a &lt;code&gt;SemaphoreSlim&lt;/code&gt; so two commands cannot race over transient shell state.&lt;/p&gt;

&lt;p&gt;This design has an honest constraint: pixel capture can only reproduce what is visible. The target must remain unobscured. That tradeoff is preferable to pretending every Visual Studio surface can be rendered reliably through WPF.&lt;/p&gt;

&lt;h2&gt;Menus and modal dialogs complicate threading&lt;/h2&gt;

&lt;p&gt;Menus, context menus, cascading flyouts, and modal dialogs are even more temporary. CodeShot starts a five-second countdown so the user can open the desired surface, then performs discovery and capture off the Visual Studio UI thread.&lt;/p&gt;

&lt;p&gt;That last part matters for modal dialogs. A modal WPF or Windows Forms dialog owns the UI thread while it is open. Resuming the delayed operation on that thread would postpone capture until the dialog had already closed.&lt;/p&gt;

&lt;p&gt;The capture code examines the foreground window and the window under the pointer. UI Automation identifies visible menu items and expanded flyouts, while Win32 window enumeration finds native &lt;code&gt;#32768&lt;/code&gt; menu windows. DWM extended frame bounds are preferred when available so shadows and invisible borders do not become part of the result.&lt;/p&gt;

&lt;h2&gt;Let Visual Studio own the command experience&lt;/h2&gt;

&lt;p&gt;The tool window itself uses &lt;code&gt;BaseToolWindow&lt;/code&gt; from the Community Visual Studio Toolkit, but its toolbar, menus, context-menu command, icons, and keyboard bindings are declared through VSCT.&lt;/p&gt;

&lt;p&gt;The tool window's persistence GUID also acts as a command scope. Combined with &lt;code&gt;ProvideKeyBindingTable&lt;/code&gt;, this allows familiar shortcuts such as Ctrl+C, Ctrl+S, Ctrl+Z, and Ctrl+Y to operate inside CodeShot without replacing their global Visual Studio behavior.&lt;/p&gt;

&lt;p&gt;That is a broadly useful extension pattern: when a feature belongs to Visual Studio chrome, use Visual Studio commands and command scopes instead of recreating the experience with WPF buttons and global key handlers.&lt;/p&gt;

&lt;h2&gt;Small boundaries make the extension easier to maintain&lt;/h2&gt;

&lt;p&gt;The first version concentrated substantial behavior in the tool-window control. As the capture modes grew, reusable pieces were extracted for selection normalization, PNG encoding, file naming, atomic saving, font discovery, capture sizing, and bounded history.&lt;/p&gt;

&lt;p&gt;Those boundaries also made the non-Visual Studio portions testable with ordinary MSTest tests. Visual Studio integration remains in the package and capture layers, while geometry, naming, indentation, and state transitions can be exercised without launching an experimental instance.&lt;/p&gt;

&lt;p&gt;The main lesson from CodeShot is that image encoding is the easy part. A Visual Studio extension like this lives at the boundaries between editor snapshots, MEF services, WPF layout, COM frames, UI Automation, Win32 windows, the clipboard, and the shell's threading rules. Treating each boundary explicitly makes the workflow feel simple even when the implementation is not.&lt;/p&gt;

&lt;p&gt;You can install &lt;a href="https://marketplace.visualstudio.com/items?itemName=madskristensen.CodeShot"&gt;CodeShot from the Visual Studio Marketplace&lt;/a&gt; or inspect the complete implementation in the &lt;a href="https://github.com/madskristensen/CodeShot"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;</description>
  <author>test@example.com</author>
  <category>visual studio</category>
  <category>vsix</category>
  <guid isPermaLink="false">https://www.madskristensen.net/blog/building-codeshot-turning-visual-studio-editor-state-into-a-shareable-png/</guid>
  <pubDate>Fri, 07 Aug 2026 19:07:37 GMT</pubDate>
</item>
<item>
  <title>Schemastore.org: The journey of a quiet revolution</title>
  <link>https://www.madskristensen.net/blog/schemastore-journey-of-a-quiet-revolution-in-development/</link>
  <description>&lt;p class="MsoNormal"&gt;Let me take you back to a moment that might feel familiar: you&amp;rsquo;re staring at a JSON configuration file, wrestling with field names and structure, hoping you&amp;rsquo;ve gotten it right. Your IDE is offering no help, and documentation is scattered or nonexistent. Frustrating, right?&lt;/p&gt;
&lt;p class="MsoNormal"&gt;Years ago, I found myself in that exact spot one too many times. As I sat there, tweaking and testing, it hit me: there had to be a better way. Little did I know that moment of frustration would spark an idea that would evolve into what you know today as &lt;strong&gt;&lt;a href="https://www.schemastore.org/json/"&gt;Schemastore.org&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p class="MsoNormal"&gt;Back then, JSON schemas weren&amp;rsquo;t the ubiquitous helpers they are now. Sure, the technology existed, but it wasn&amp;rsquo;t easy to find schemas for the tools you were using. If you needed a schema for your project, you either had to dig through obscure documentation or&amp;mdash;more often&amp;mdash;roll up your sleeves and build it yourself.&lt;/p&gt;
&lt;p class="MsoNormal"&gt;It was a time sink. And worse, it was entirely avoidable.&lt;/p&gt;
&lt;p class="MsoNormal"&gt;The real problem wasn&amp;rsquo;t the complexity of schemas; it was the lack of a centralized place to find them. Developers were solving the same problems over and over, each in isolation, when a shared resource could have made all the difference.&lt;/p&gt;
&lt;h2&gt;The Turning Point&lt;/h2&gt;
&lt;p class="MsoNormal"&gt;So, I decided to create that shared resource. The idea was simple: &lt;strong&gt;a public &lt;a href="https://github.com/schemastore/schemastore/"&gt;repository&lt;/a&gt; where anyone could find and contribute JSON schemas&lt;/strong&gt;. It wouldn&amp;rsquo;t be fancy&amp;mdash;just a straightforward way to save developers from hours of unnecessary frustration.&lt;/p&gt;
&lt;p class="MsoNormal"&gt;When I launched Schemastore.org, I didn&amp;rsquo;t have big expectations. I thought it might help a few people, but I never imagined how quickly it would resonate with the development community.&lt;/p&gt;
&lt;h2&gt;The Rise of a Community&lt;/h2&gt;
&lt;p class="MsoNormal"&gt;What happened next was extraordinary. Developers from all over the world began contributing. They sent pull requests for schemas I&amp;rsquo;d never heard of fixed errors I hadn&amp;rsquo;t spotted, and offered suggestions that made the site better in ways I couldn&amp;rsquo;t have predicted.&lt;/p&gt;
&lt;p class="MsoNormal"&gt;It wasn&amp;rsquo;t just about the schemas anymore; it was about the community.&lt;/p&gt;
&lt;p class="MsoNormal"&gt;Over the years, Schemastore.org has grown into a critical resource, quietly powering tools like Visual Studio Code, GitHub Actions, IntelliJ IDEA, and many others. The stats are humbling: over 1500 contributors, 4000 pull requests, 1000 schema files, and millions of developers who&amp;rsquo;ve benefited from the work.&lt;/p&gt;
&lt;p class="MsoNormal"&gt;That results in 2 TB of schema files being served to IDEs, validators, and other system every single day to support JSON, YAML, and TOML files.&lt;/p&gt;
&lt;p class="MsoNormal"&gt;But the real story isn&amp;rsquo;t in the numbers. It&amp;rsquo;s in the ethos: a shared belief that open collaboration can solve even the most frustrating problems.&lt;/p&gt;
&lt;h2&gt;The Challenges Today&lt;/h2&gt;
&lt;p class="MsoNormal"&gt;Of course, growth comes with challenges. Keeping Schemastore.org running smoothly requires time, effort, and resources. While it&amp;rsquo;s still volunteer-driven, the infrastructure and maintenance costs have grown alongside its popularity.&lt;/p&gt;
&lt;p class="MsoNormal"&gt;And here&amp;rsquo;s the thing: for-profit organizations are among the biggest beneficiaries of Schemastore.org. They use the schemas to validate configurations, streamline workflows, and, ultimately, save time and money.&lt;/p&gt;
&lt;h2&gt;The Ask&lt;/h2&gt;
&lt;p class="MsoNormal"&gt;If you&amp;rsquo;re part of such an organization&amp;mdash;whether you&amp;rsquo;re contributing schemas or relying on them&amp;mdash;I have a request: &lt;strong&gt;consider sponsoring Schemastore.org&lt;/strong&gt;.&lt;/p&gt;
&lt;p class="MsoNormal"&gt;Your support would help cover hosting, maintenance, and development costs, ensuring this resource remains free and open for the entire community. It&amp;rsquo;s a small way to give back to something that likely saves you (and your team) time and effort every day.&lt;/p&gt;
&lt;p class="MsoNormal"&gt;You can find &lt;a href="https://github.com/sponsors/madskristensen"&gt;sponsorship details on the site&lt;/a&gt;, or feel free to reach out to me directly.&lt;/p&gt;
&lt;h2&gt;The Resolution&lt;/h2&gt;
&lt;p class="MsoNormal"&gt;Looking back, it&amp;rsquo;s amazing to see how far Schemastore.org has come. What started as a small project has grown into something much bigger &amp;ndash; a shared resource, built by and for the community, quietly making life easier for developers everywhere.&lt;/p&gt;
&lt;p class="MsoNormal"&gt;This journey isn&amp;rsquo;t over. With your help, we can keep Schemastore.org thriving, growing, and helping developers for years to come.&lt;/p&gt;
&lt;p class="MsoNormal"&gt;Thank you for being part of this story, whether as a user, a contributor, or someone who simply appreciates the effort.&lt;/p&gt;
&lt;p class="MsoNormal"&gt;Here&amp;rsquo;s to solving problems &amp;ndash; one schema at a time.&lt;/p&gt;</description>
  <author>test@example.com</author>
  <category>json</category>
  <category>yaml</category>
  <category>toml</category>
  <guid isPermaLink="false">https://www.madskristensen.net/blog/schemastore-journey-of-a-quiet-revolution-in-development/</guid>
  <pubDate>Mon, 09 Dec 2024 21:41:37 GMT</pubDate>
</item>
<item>
  <title>Receive a notification when the dishwasher finishes</title>
  <link>https://www.madskristensen.net/blog/receive-a-notification-when-the-dishwasher-finishes/</link>
  <description>&lt;p&gt;At our house, we use home automation for a lot of different things. One of them is the ability to get a quick overview of the state of our appliances such as the washer, dryer and dishwasher. That way we don’t accidentally forget to turn over the laundry or empty the dishwasher. &lt;/p&gt;  &lt;p&gt;A red tile on our dashboard means something is ready for us to take action on – in this case that would be emptying the dishwasher after it finishes. See &lt;a href="https://madskristensen.net/blog/the-dashboard-for-our-smart-home/"&gt;earlier blog post&lt;/a&gt; on how I built the dashboard below.&lt;/p&gt;  &lt;p&gt;&lt;img title="clip_image002" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" border="0" alt="clip_image002" src="https://madskristensen.net/posts/files/clip_image002_637201598987747999.jpg" width="845" height="528" /&gt;&lt;/p&gt;  &lt;p&gt;There are lots of &lt;a href="https://community.smartthings.com/search?q=laundry"&gt;tutorials&lt;/a&gt; on how to hook a “dumb” washer and dryer up to any home automation system, but I haven’t found any for the dishwasher that doesn’t involve figuring out how to hook up a &lt;a href="https://www.amazon.com/Z-Wave-Power-Switch-ZEN15-Humidifiers/dp/B07578W7KY"&gt;power meter&lt;/a&gt; to it. &lt;/p&gt;  &lt;p&gt;So, after a bit of trial and error I was able to do a highly accurate reporting mechanism using only a $20 multipurpose sensor that I had lying around. &lt;/p&gt;  &lt;h4&gt;The sensor&lt;/h4&gt;  &lt;p&gt;You need one or more sensors capable of sensing tilt, acceleration and temperature. I used the $20 &lt;a href="https://www.amazon.com/Samsung-SmartThings-Multipurpose-Sensor-GP-U999SJVLAAA/dp/B07F956F3B"&gt;Samsung Multipurpose sensor&lt;/a&gt; which integrates seamlessly into my SmartThings home automation system. &lt;/p&gt;  &lt;p&gt;Attach the sensor along the side of the dishwasher at a place where it doesn’t get in the way. My wife didn’t notice the sensor until I asked her to look several days later. &lt;/p&gt;  &lt;p&gt;&lt;img title="dishwasher-sensor" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" border="0" alt="dishwasher-sensor" src="https://madskristensen.net/posts/files/dishwasher-sensor_637201600770407300.png" width="600" height="729" /&gt;&lt;/p&gt;  &lt;p&gt;The key is that the sensor needs to be located such that it can measure the raise in temperature from inside the dishwasher as it runs through its heated drying cycle. For your dishwasher, it might be higher up or even underneath. &lt;/p&gt;  &lt;p&gt;Then go into the configuration of the sensor and specify it to be used on a garage door. &lt;/p&gt;  &lt;p&gt;&lt;img title="dishwasher-config" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" border="0" alt="dishwasher-config" src="https://madskristensen.net/posts/files/dishwasher-config_637201600775514433.png" width="600" height="812" /&gt;&lt;/p&gt;  &lt;p&gt;All that does is to use the tilt sensor to determine if it’s open or closed instead of the magnet. That way, you must open the dishwasher door all the way down to horizontal level before it registers as open. That’s what we want.&lt;/p&gt;  &lt;h3&gt;The automation logic&lt;/h3&gt;  &lt;p&gt;I’ve created a virtual switch called &lt;i&gt;Dishwasher&lt;/i&gt; that I use to store the state of the dishwasher cycles which is what I use for the dashboard. It’s turned on when the dishwasher finishes and turned back off once it’s emptied. &lt;/p&gt;  &lt;p&gt;It uses the fact that a bit of vibration occurs early on in the cycle, and temperature rapidly dropping after it finishes. The name of the multipurpose sensor is &lt;i&gt;Dishwasher Sensor&lt;/i&gt;. Here’s the logic:&amp;#160;&amp;#160;&amp;#160; &lt;/p&gt;  &lt;p&gt;&lt;img title="dishwasher-piston" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" border="0" alt="dishwasher-piston" src="https://madskristensen.net/posts/files/dishwasher-piston_637201600780904464.png" width="600" height="783" /&gt;&lt;/p&gt;  &lt;p&gt;Notice how it uses the variable &lt;i&gt;isRunning&lt;/i&gt; to keep track on when the dishwasher is running.&lt;/p&gt;  &lt;p&gt;That’s it. Pretty simple, but it may require a bit of trial and error on where to place the sensor on the dishwasher door.&lt;/p&gt;</description>
  <author>test@example.com</author>
  <category>home automation</category>
  <guid isPermaLink="false">https://www.madskristensen.net/blog/receive-a-notification-when-the-dishwasher-finishes/</guid>
  <pubDate>Wed, 18 Mar 2020 20:25:05 GMT</pubDate>
</item>
<item>
  <title>Hiding electronics behind fake books</title>
  <link>https://www.madskristensen.net/blog/hiding-electronics-behind-fake-books/</link>
  <description>&lt;p&gt;After showing some pictures of our &lt;a href="https://madskristensen.net/blog/the-dashboard-for-our-smart-home/"&gt;smart home dashboard&lt;/a&gt;, questions started coming in about the cables going in behind the books. Where is the modem and what else is going on? Spoiler alert, the books are fake. &lt;/p&gt;  &lt;p&gt;Here you see a couple of cables going in behind the books on the top shelve.&lt;/p&gt;  &lt;p&gt;&lt;img title="fake books 1" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" border="0" alt="fake books 1" src="https://madskristensen.net/posts/files/fake%20books%201_637161973355565458.jpg" width="1024" height="850" /&gt;&lt;/p&gt;  &lt;p&gt;Removing the books reveal a tangled mess of cords, power bricks, a modem, and a home automation hub.&lt;/p&gt;  &lt;p&gt;&lt;img title="fake books 2" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" border="0" alt="fake books 2" src="https://madskristensen.net/posts/files/fake%20books%202_637161973364015977.jpg" width="1010" height="844" /&gt;&lt;/p&gt;  &lt;p&gt;The fake books are hollow and can hold quite the amount of stuff, as you can see below. Looking at the books from above shows just how simple they’re built.&lt;/p&gt;  &lt;p&gt;&lt;img title="fake books 3" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" border="0" alt="fake books 3" src="https://madskristensen.net/posts/files/fake%20books%203_637161973370084489.jpg" width="1024" height="545" /&gt;&lt;/p&gt;  &lt;p&gt;This is easy to make yourself if you can find some decorative books you like. You can also buy it (&lt;a href="https://www.amazon.com/Covobox-electronics-outlets-documents-original/dp/B074PDZLQX/"&gt;Covobox on Amazon&lt;/a&gt;) and customize the size (width) and the colors of the books. For reference, we got the 14 inch one and that is probably the smallest I would recommend. &lt;/p&gt;  &lt;p&gt;We’ve had this for a couple of years, and it holds up great.&lt;/p&gt;</description>
  <author>test@example.com</author>
  <category>home automation</category>
  <guid isPermaLink="false">https://www.madskristensen.net/blog/hiding-electronics-behind-fake-books/</guid>
  <pubDate>Sat, 01 Feb 2020 23:42:17 GMT</pubDate>
</item>
<item>
  <title>The dashboard for our smart home</title>
  <link>https://www.madskristensen.net/blog/the-dashboard-for-our-smart-home/</link>
  <description>&lt;p&gt;I’ve had some questions lately about how we keep track of all the home automation devices spread across the house. So, I thought I’d share how our dashboard is set up. The dashboard is the web-based &lt;a href="https://sharptools.io/"&gt;SharpTools&lt;/a&gt; that runs in &lt;a href="https://www.ozerov.de/fully-kiosk-browser/"&gt;Fully Kiosk Browser&lt;/a&gt; on an &lt;a href="https://www.amazon.com/dp/B07KD6BTCZ"&gt;Amazon Fire Tablet HD 10&lt;/a&gt; which is magnetically attached to the wall. &lt;/p&gt;  &lt;p&gt;&lt;a href="https://madskristensen.net/posts/files/20200130_165855165_iOS_637160068674899332.jpg"&gt;&lt;img title="20200130_165855165_iOS" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" border="0" alt="20200130_165855165_iOS" src="https://madskristensen.net/posts/files/20200130_165855165_iOS_thumb_637160068681994799.jpg" width="1280" height="956" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Since it hangs so close to shelving, the charging cable is nicely hidden, so we didn't need to install additional electrical outlets behind the tablet.&lt;/p&gt;  &lt;p&gt;&lt;a href="https://madskristensen.net/posts/files/20200130_165901366_iOS_637160068686605643.jpg"&gt;&lt;img title="20200130_165901366_iOS" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" border="0" alt="20200130_165901366_iOS" src="https://madskristensen.net/posts/files/20200130_165901366_iOS_thumb_637160068691533235.jpg" width="1280" height="956" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Though the tiles looked blue above due to lighting and old phone camera, all tiles are actually gray, and red is used for the ones that need our attention when certain events occur - like when the washer is done and we need to move the clothes to the dryer. This helps keep the visual noise to a minimum and the red is really easy to see from across the room.&lt;/p&gt;    &lt;p&gt;The bottom row is half way below the fold and we utilize this for both navigation to other dashboard (the left 3 bottom tiles) and some infrequent commands. The calendar is well suited for being cut off since it easily scrolls to reveal future calendar entries without scrolling the whole screen.&lt;/p&gt;  &lt;p&gt;&lt;a href="https://madskristensen.net/posts/files/20200130_040848334_iOS_637160068695832579.jpg"&gt;&lt;img title="20200130_040848334_iOS" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" border="0" alt="20200130_040848334_iOS" src="https://madskristensen.net/posts/files/20200130_040848334_iOS_thumb_637160068700727722.jpg" width="1280" height="794" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;The first sub-dashboard is for the lights around the house. We use orange as a neutral color instead of green/red or similar. That's because it is neither good nor bad if a light is on or off. So orange in this case just means that something is active or on.&lt;/p&gt;  &lt;p&gt;&lt;a href="https://madskristensen.net/posts/files/20200130_040906613_iOS_637160068706563715.jpg"&gt;&lt;img title="20200130_040906613_iOS" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" border="0" alt="20200130_040906613_iOS" src="https://madskristensen.net/posts/files/20200130_040906613_iOS_thumb_637160068712057096.jpg" width="1280" height="794" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;The second sub-dashboard tell us the status of more security related information. It tells us what external doors and windows are open, if the door is locked, the cameras armed and who's home.&lt;/p&gt;  &lt;p&gt;&lt;a href="https://madskristensen.net/posts/files/20200130_145903231_iOS_637160068716826729.jpg"&gt;&lt;img title="20200130_145903231_iOS" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" border="0" alt="20200130_145903231_iOS" src="https://madskristensen.net/posts/files/20200130_145903231_iOS_thumb_637160068721040256.jpg" width="1280" height="794" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;It also has a link to a sub-sub-dashboard for showing battery status of our various devices. This uses the Battery Tile and will change color as the battery levels get below 30% and again below 10%. None of my devices are low on battery - thanks to this dashboard.&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;/p&gt;  &lt;p&gt;&lt;a href="https://madskristensen.net/posts/files/20200130_150015824_iOS_637160068725228694.jpg"&gt;&lt;img title="20200130_150015824_iOS" style="display: inline; background-image: none;" border="0" alt="20200130_150015824_iOS" src="https://madskristensen.net/posts/files/20200130_150015824_iOS_thumb_637160068729956912.jpg" width="1284" height="804" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;The third sub-dashboard is temperature and weather information from around the house.&lt;/p&gt;  &lt;p&gt;&lt;a href="https://madskristensen.net/posts/files/20200130_041009032_iOS_637160068747908242.jpg"&gt;&lt;img title="20200130_041009032_iOS" style="border: 0px currentcolor; border-image: none; display: inline; background-image: none;" border="0" alt="20200130_041009032_iOS" src="https://madskristensen.net/posts/files/20200130_041009032_iOS_thumb_637160068756270625.jpg" width="1280" height="794" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;That’s it. I tried to keep it simple, but still containing all the information I need. What do you think?&lt;/p&gt;</description>
  <author>test@example.com</author>
  <category>home automation</category>
  <guid isPermaLink="false">https://www.madskristensen.net/blog/the-dashboard-for-our-smart-home/</guid>
  <pubDate>Thu, 30 Jan 2020 18:18:23 GMT</pubDate>
</item>
<item>
  <title>Home automation sensors</title>
  <link>https://www.madskristensen.net/blog/home-automation-sensors/</link>
  <description>&lt;p&gt;Motion, contact, and light sensors are the most commonly used sensors for home automations in my house. With these sensors, you can make a &lt;a href="https://madskristensen.net/blog/home-automation-ideas/"&gt;wide variety of automations&lt;/a&gt;. But there are so many different manufacturers, protocols, and price points that it can be hard to choose which devices to buy and use.&lt;/p&gt;
&lt;p&gt;When setting up automations, check out these set of &lt;a href="https://madskristensen.net/blog/home-automation-best-practices/"&gt;best practices&lt;/a&gt; to avoid common mistakes. It boils down to having automations that do the same thing as you (and your guests) would do before the automation existed. For instance, turn on the light when entering the bedroom, but not if it&amp;rsquo;s filled with sunlight.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sensor speed&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before we dive in, I want to make a note about sensor speed. For lighting automations, speed is usually the most important factor to consider. When entering a dark room, most people instinctively reach for the light switch. It&amp;rsquo;s in their muscle memory.&lt;/p&gt;
&lt;p&gt;When a lighting automation is slow, that muscle memory triggers, and they reach for the switch. Just before pressing the switch, the automation turned on the light, so they end up turning the light switch off.&lt;/p&gt;
&lt;p&gt;When the lighting automation is super-fast, that muscle memory never triggered in the first place and the automation feels magical.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When using Samsung SmartThings hub, make sure to run all your lighting automations in the Smart Lighting SmartApp. For all involved devices (usually switches/bulbs + sensors) make sure they use local device handlers. Then the automation will execute locally without accessing the internet. The speed difference from local to internet execution is significant.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&amp;rsquo;m not affiliated with any manufacturer or reseller. I&amp;rsquo;ve tried enough sensors to give me an idea about which ones work better than others for my needs. You might have a different experience or know of sensors that I haven&amp;rsquo;t tried. In that case, please share that in the comments below.&lt;/p&gt;
&lt;h2&gt;&lt;span style="font-weight: normal;"&gt;Motion sensors&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;The most important factors to consider when buying a motion sensor are speed, battery life, and design.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zigbee vs. Z-Wave&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Of all the sensors I&amp;rsquo;ve tried, the Zigbee ones are the fastest. I don't know why that is, but &lt;a href="https://community.smartthings.com/t/faq-where-to-locate-motion-sensor-for-fastest-response/55798/6"&gt;others have experienced the same thing&lt;/a&gt;. So, for automations where speed is important such as turning lights on when entering a room, I would go with a Zigbee device every single time. The difference in speed is very noticeable.&lt;/p&gt;
&lt;p&gt;For automations where speed isn&amp;rsquo;t as important, such as for detecting when motion has stopped, then battery life and design matters more.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Placements&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Motion sensors look a little bit like small spy cams, so I try to make them as invisible as I can to not freak out my guests. Especially the ones placed in the bathrooms. Good places to but them are corners just below the ceiling.&lt;/p&gt;
&lt;p&gt;For super-fast detection when someone enters a room, place the motion sensor on the ceiling close to the wall directly above the door pointing downwards.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Which one to get&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If I could have only a single type of motion sensor, I would choose the &lt;a href="https://www.amazon.com/gp/product/B07F8ZHBLS/"&gt;Samsung SmartThings Motion Sensor&lt;/a&gt;. It magnetically attaches to its base and the battery life is superb.&lt;/p&gt;
&lt;h2&gt;&lt;span style="font-weight: normal;"&gt;Contact sensors&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Also known as door/window sensors. This type of sensor consists of 2 pieces &amp;ndash; a magnet and a sensor. When the magnet moves away from the sensor, it&amp;rsquo;s in its &amp;ldquo;open&amp;rdquo; state and when they are together it&amp;rsquo;s &amp;ldquo;closed&amp;rdquo;. Again speed, battery life, and design are the most important features to consider.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zigbee vs. Z-Wave&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Just like with the motion sensor, speed can be the deciding factor. If opening a door should trigger the lights to come on, then Zigbee is the way to go. For everything else, battery life and design are more important. For me, that means I use Zigbee devices for doors and Z-Wave devices with larger batteries for windows.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Placements&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For doors, place them on the side of the door that is away from the main areas in the house. For instance, put them inside the closet where nobody can see them when closed. Put them on the bedroom side of the door instead of the hallway side.&lt;/p&gt;
&lt;p&gt;For windows, place them where they are hardest to see. You can&amp;rsquo;t really hide them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Color&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Some manufacturers make different colored sensors. Try to match the color of the sensor with that of where you put them. This makes them more invisible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Which one to get&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For doors, I use the &lt;a href="https://www.amazon.com/gp/product/B06XDJ3KYC/"&gt;Visonic MCT-340 E&lt;/a&gt; which is super-fast and relatively small compared to most other contact sensors. For windows or other places where speed isn&amp;rsquo;t an issue and battery life matters more, the &lt;a href="https://www.amazon.com/dp/B079MB2FQN/"&gt;NEO Z-Wave sensor&lt;/a&gt; is great. My shower door has a brown metal frame, so for that I use the slightly bigger &lt;a href="https://www.amazon.com/dp/B01N5HB4U5/"&gt;Z-Wave Rare Earth Magnet sensor&lt;/a&gt; which comes in brown.&lt;/p&gt;
&lt;h2&gt;&lt;span style="font-weight: normal;"&gt;Light sensors&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;With a light or illuminance sensor, we can adjust our automation based on the amount of light present in the room. That way we can avoid turning on the lights when the room is already filled with sunlight. Or we can adjust the dimming level of a light switch to the amount of light presently in the room.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zigbee vs. Z-Wave&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For me, it usually comes down to speed. In this case, it probably doesn&amp;rsquo;t matter since light measurement isn&amp;rsquo;t done in real-time but usually captured every 1-5 minutes depending on the sensor. So, battery life and design are more important to consider than speed.&lt;/p&gt;
&lt;p&gt;Usually, light sensors don&amp;rsquo;t come as standalone sensors, but as part of multi-sensors that also does motion and temperature sensing. At the time of writing, I have yet to find a light sensor that also has a super-fast motion sensor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Placements&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You could place a light sensor in each room, but that would most likely be overkill. Unless you already have a multi-sensor in the rooms with light sensing capabilities. Instead, what I&amp;rsquo;ve done is to place one sensor on each side of the house. So, no matter what room I automate, I know the amount of light coming to the windows on that side of the house.&lt;/p&gt;
&lt;p&gt;Place them in the window close to the glass so curtains don&amp;rsquo;t get in between them and the window itself.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Which one to get&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I use the &lt;a href="https://www.amazon.com/dp/B01M1NBH3D/"&gt;Dome Z-Wave Motion/Light sensor&lt;/a&gt; because it is relatively cheap and gives consistent light level readings. It&amp;rsquo;s important to notice that I don&amp;rsquo;t use the motion sensor in this device at all. It&amp;rsquo;s too slow and it doesn&amp;rsquo;t allow me to place it in the best position for light sensing.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When using SmartThings Hub, use the &lt;em&gt;Z-Wave Temp/Light Sensor&lt;/em&gt; device handler for this sensor. It allows it to run locally.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I hope you found this helpful. Remember to check out more &lt;a href="https://madskristensen.net/blog/home-automation-ideas/"&gt;home automation ideas&lt;/a&gt; and the &lt;a href="https://madskristensen.net/blog/home-automation-best-practices/"&gt;best practices&lt;/a&gt;.&lt;/p&gt;</description>
  <author>test@example.com</author>
  <category>home automation</category>
  <guid isPermaLink="false">https://www.madskristensen.net/blog/home-automation-sensors/</guid>
  <pubDate>Tue, 16 Apr 2019 15:22:59 GMT</pubDate>
</item></channel>
</rss>