<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: OnePassApps</title>
    <description>The latest articles on DEV Community by OnePassApps (@onepassapps).</description>
    <link>https://dev.to/onepassapps</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4222432%2F971441a4-25b9-4a1f-9985-337cdf95d706.png</url>
      <title>DEV Community: OnePassApps</title>
      <link>https://dev.to/onepassapps</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/onepassapps"/>
    <language>en</language>
    <item>
      <title>Remove Duplicate Lines in the Browser: Case, Empty Lines and Which Copy You Keep</title>
      <dc:creator>OnePassApps</dc:creator>
      <pubDate>Tue, 03 Nov 2026 04:59:19 +0000</pubDate>
      <link>https://dev.to/onepassapps/remove-duplicate-lines-in-the-browser-case-empty-lines-and-which-copy-you-keep-4b77</link>
      <guid>https://dev.to/onepassapps/remove-duplicate-lines-in-the-browser-case-empty-lines-and-which-copy-you-keep-4b77</guid>
      <description>&lt;p&gt;I work on OnePassApps. We build browser studios that do their work in the tab instead of uploading your files, and Text Studio is the one for plain text: eight tools on one tab bar - Clean, Case, Duplicate lines, Sort lines, Find &amp;amp; replace, Diff, Markdown and Base64.&lt;/p&gt;

&lt;p&gt;The Duplicate lines tab is the one that looks trivial until you ask what "duplicate" means. Is &lt;code&gt;Apple&lt;/code&gt; the same line as &lt;code&gt;apple&lt;/code&gt;? Is &lt;code&gt;" apple"&lt;/code&gt; (with a leading space) the same as &lt;code&gt;apple&lt;/code&gt;? When a line repeats three times, which copy survives, and where does it end up in the output? This is about those rules rather than the button: the three options, and what the tool actually does with case, empty lines and repeated lines.&lt;/p&gt;

&lt;p&gt;The page describes itself in one line: "Count, clean, compare and convert text locally. Your content stays in this browser tab."&lt;/p&gt;

&lt;h2&gt;
  
  
  What the tab gives you
&lt;/h2&gt;

&lt;p&gt;One panel, three steps. &lt;strong&gt;01 Input text&lt;/strong&gt; - a box with a live counter (2,000,000 characters on this tab) and a live stats row: Words, Characters, Sentences, Lines and UTF-8 bytes. &lt;strong&gt;02 Options&lt;/strong&gt; - three controls: Keep duplicate order, Case-sensitive and Keep every empty line. &lt;strong&gt;03 Result&lt;/strong&gt; - "Unique lines shows on the stage, ready to copy, download or use as the new input."&lt;/p&gt;

&lt;p&gt;The action button reads "Remove duplicate lines" and becomes "Remove again" once a run has happened. The result panel then reports the size of what it produced and how long it took ("Duplicates removed in 0.1 s"), and offers Use as input, Download .txt and Copy. The stage footer reads "Text transformed locally." and the panel says "Nothing leaves this tab."&lt;/p&gt;

&lt;p&gt;You can drop a file in instead of pasting: the drop area takes ".txt, .md, .csv and other text up to 8 MiB", and the tab says "Processing stays in this browser." The file input itself accepts &lt;code&gt;.txt,.text,.md,.markdown,.csv,.tsv,.json,.log,.xml,.yaml,.yml,.html,.srt,.vtt&lt;/code&gt;, plus the &lt;code&gt;text/plain&lt;/code&gt;, &lt;code&gt;text/markdown&lt;/code&gt; and &lt;code&gt;text/csv&lt;/code&gt; types.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three options
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Keep duplicate order&lt;/strong&gt; is a dropdown with two entries: "First occurrence" and "Last occurrence". It ships on First occurrence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case-sensitive&lt;/strong&gt; ships on (ticked), and &lt;strong&gt;Keep every empty line&lt;/strong&gt; ships on (ticked) as well. Those two defaults are the whole reason the tab is worth a look: a default-on case-sensitive match will happily keep &lt;code&gt;Apple&lt;/code&gt; and &lt;code&gt;apple&lt;/code&gt; as two different lines, and a default-on empty-line setting keeps your blank lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run 1: the defaults
&lt;/h2&gt;

&lt;p&gt;Input, eight lines including one empty line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apple
banana
apple
Apple
 apple
banana

apple
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the defaults (First occurrence, Case-sensitive on, Keep every empty line on), the result stage reported "Duplicates removed in 0.1 s" and "26 characters · 5 lines". The output was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apple
banana
Apple
 apple
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that carefully, because every line in it is a decision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;apple&lt;/code&gt; appears three times in the input and survives &lt;strong&gt;once&lt;/strong&gt;. The first occurrence won, so the output keeps the lowercase spelling, not the capitalised one that came later.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Apple&lt;/code&gt; survives as its own line. Case-sensitive is on, so it is a different line from &lt;code&gt;apple&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apple&lt;/code&gt; (with a leading space) survives as its own line too. The comparison is on the exact line, so a leading space makes it different.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;banana&lt;/code&gt; appears twice and survives once.&lt;/li&gt;
&lt;li&gt;The empty line survives, because Keep every empty line is on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run 2: case-insensitive
&lt;/h2&gt;

&lt;p&gt;Untick Case-sensitive and run the same input again. The result is "20 characters · 4 lines":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apple
banana
 apple
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Apple&lt;/code&gt; is now folded into &lt;code&gt;apple&lt;/code&gt;, so the output has one fewer line. Note the leading-space line still stands on its own: the tool compares the line as it is, it does not trim whitespace before comparing. So "case-insensitive" here means exactly that - it changes the letter comparison and nothing else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run 3: and the empty line goes too
&lt;/h2&gt;

&lt;p&gt;Untick Keep every empty line as well and the same input gives "19 characters · 3 lines":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apple
banana
 apple
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three lines now. The empty line is gone, the case difference is gone, and the whitespace difference is still there. Those are three separate switches doing three separate jobs, which is worth knowing before you assume "de-duplicate" means one particular thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run 4: which copy survives, and where does it land
&lt;/h2&gt;

&lt;p&gt;Keep duplicate order decides both. With the input &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;First occurrence&lt;/strong&gt; gives &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt; - the surviving copy keeps the &lt;em&gt;first&lt;/em&gt; position the line ever had.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Last occurrence&lt;/strong&gt; gives &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt; - the surviving copy moves to the &lt;em&gt;last&lt;/em&gt; position that line appeared in, and the other lines shift up to close the gap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both runs report the same size, "5 characters · 3 lines". The set of unique lines is identical; only the order differs. That is the whole difference between the two dropdown entries.&lt;/p&gt;

&lt;p&gt;The same thing with a line that repeats three times, input &lt;code&gt;keep&lt;/code&gt;, &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;keep&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, &lt;code&gt;keep&lt;/code&gt;, under Last occurrence, gives &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, &lt;code&gt;keep&lt;/code&gt;: the surviving copy lands at the end, where the third occurrence was.&lt;/p&gt;

&lt;h2&gt;
  
  
  The output file
&lt;/h2&gt;

&lt;p&gt;The result panel offers "Download .txt", and that button really writes a file. Reading the blob it creates back gives a &lt;code&gt;text/plain;charset=utf-8&lt;/code&gt; payload of exactly 26 bytes for the first run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apple
banana
Apple
 apple
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;26 bytes, matching the "26 characters · 5 lines" the panel reported. The trailing newline on the last line is in the file, and the empty line from the input is not in it as an empty line - it is the newline that separates &lt;code&gt;apple&lt;/code&gt; from the end of the file, which is why the panel counts 5 lines while the text shows 4 visible ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the tab does not do
&lt;/h2&gt;

&lt;p&gt;It does not sort. The output comes out in the order the surviving lines were in the input, and the Sort lines tab is the separate tool for reordering. It does not trim or normalise whitespace, and it does not rewrite the lines it keeps - the output is made of the exact strings that were in the input.&lt;/p&gt;

&lt;p&gt;It also does not touch the original. "Use as input" moves the result into the input box so you can run another tool on it, and your pasted text or your file on disk is never modified.&lt;/p&gt;

&lt;p&gt;Try it on the &lt;a href="https://app.onepassapps.com/studios/text?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=text-duplicate-lines&amp;amp;utm_content=text-studio-link" rel="noopener noreferrer"&gt;Text Studio&lt;/a&gt;, and the &lt;a href="https://app.onepassapps.com/guides?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=text-duplicate-lines&amp;amp;utm_content=guides-link" rel="noopener noreferrer"&gt;guides page&lt;/a&gt; covers the rest of the studios. The standard plan is free right now - $0/month, 100,000 credits every 30 days, no card required.&lt;/p&gt;

</description>
      <category>text</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>tools</category>
    </item>
    <item>
      <title>Build a PWA Icon Pack in the Browser: 8 Sizes, a Manifest and a README</title>
      <dc:creator>OnePassApps</dc:creator>
      <pubDate>Tue, 03 Nov 2026 04:41:53 +0000</pubDate>
      <link>https://dev.to/onepassapps/build-a-pwa-icon-pack-in-the-browser-8-sizes-a-manifest-and-a-readme-23mh</link>
      <guid>https://dev.to/onepassapps/build-a-pwa-icon-pack-in-the-browser-8-sizes-a-manifest-and-a-readme-23mh</guid>
      <description>&lt;p&gt;I work on OnePassApps. We build browser studios that do their work in the tab instead of uploading your files, and Icon Studio is the one for icons: eight tools on one tab bar - SVG to PNG, PNG to ICO, Favicon pack, App icons, Resize set, Optimize SVG, Recolour SVG and Trim / padding.&lt;/p&gt;

&lt;p&gt;The App icons tab is the one that produces a whole installable icon set rather than a single file: one image in, eight PWA sizes plus a manifest and a README, packed into one ZIP. So this is about what actually lands in that archive, and about what the tab does not do.&lt;/p&gt;

&lt;p&gt;The page describes itself in one line: "Create icons and favicon assets on this device. Files stay in your active browser tab."&lt;/p&gt;

&lt;h2&gt;
  
  
  What the tab gives you
&lt;/h2&gt;

&lt;p&gt;One panel, three steps. &lt;strong&gt;01 Source&lt;/strong&gt; - "No SVG or PNG yet. Drop one on the stage or browse for it. SVG up to 5 MiB, PNG up to 24 MiB." &lt;strong&gt;02 Pack details&lt;/strong&gt; - "Name the app for the manifest once an image is open. The pack holds 8 PWA sizes, 72 to 512 pixels." &lt;strong&gt;03 Result&lt;/strong&gt; - "A ZIP with 8 PWA PNG sizes, a manifest and a README."&lt;/p&gt;

&lt;p&gt;The action button reads "Create app icon pack". Once an image is open the panel fills in: the file name with its type and dimensions, "8 sizes, 72 to 512 px", a Purpose field that read "any" in this run, and an "Also included" line reading "Manifest, README". The only text field is the app name for the manifest, and it read "App" in this run.&lt;/p&gt;

&lt;h2&gt;
  
  
  What came out
&lt;/h2&gt;

&lt;p&gt;The source was a 512 x 512 PNG of 2.2 KiB, written by a probe script and dropped on the stage. The result panel reported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;probe-appicons-pwa-icons.zip
10 files - 18.6 KiB
Before   2.2 KiB  PNG
After    18.6 KiB ZIP
Created in 0.6 s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the eight icons, listed by size and file name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;72 x 72     icon-72x72.png
96 x 96     icon-96x96.png
128 x 128   icon-128x128.png
144 x 144   icon-144x144.png
152 x 152   icon-152x152.png
192 x 192   icon-192x192.png
384 x 384   icon-384x384.png
512 x 512   icon-512x512.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And two more entries under "ALSO IN THE ZIP":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;site.webmanifest   333 B
README.txt         109 B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The README is a single line, shown on the panel verbatim: "PWA PNG icon sizes 72, 96, 128, 144, 152, 192, 384, 512 were created with transparent backgrounds."&lt;/p&gt;

&lt;p&gt;Ten files in the archive: eight icons, a manifest, a README. The panel also offers a Dark and a Light preview next to the ZIP, so you can see the icon against both backgrounds before you take the file.&lt;/p&gt;

&lt;p&gt;The archive name follows the file you dropped in: the source was probe-appicons.png and the ZIP came out as probe-appicons-pwa-icons.zip.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this tab does not do
&lt;/h2&gt;

&lt;p&gt;It does not draw the icons. You supply one SVG or PNG and the pack resizes that image - the artwork is yours.&lt;/p&gt;

&lt;p&gt;I read the manifest's size on the panel (333 B) but did not unpack the archive in this run, so I am not going to describe what is inside that JSON beyond the byte count.&lt;/p&gt;

&lt;p&gt;The app name field is the only free-text input, and it feeds the manifest. In this run it read "App" because that is exactly what the probe typed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it runs
&lt;/h2&gt;

&lt;p&gt;Icon Studio is part of OnePassApps and it lives on the &lt;a href="https://app.onepassapps.com/studios/icons?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=app-icon-pack&amp;amp;utm_content=icon-studio-link" rel="noopener noreferrer"&gt;Icon Studio page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Everything happens in the tab: the image is read in the browser, the sizes are rendered there, and the ZIP is written out on your own machine. Standard is free right now - $0/month, 100,000 credits every 30 days, no card required, and no automatic paid subscription.&lt;/p&gt;

&lt;p&gt;Guides for the studios and the apps behind them are on the &lt;a href="https://app.onepassapps.com/guides?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=app-icon-pack&amp;amp;utm_content=guides-link" rel="noopener noreferrer"&gt;OnePassApps guides page&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>pwa</category>
      <category>icons</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Sort Lines in the Browser: Numbers, Case and the Whitespace That Decides the Order</title>
      <dc:creator>OnePassApps</dc:creator>
      <pubDate>Tue, 03 Nov 2026 03:54:32 +0000</pubDate>
      <link>https://dev.to/onepassapps/sort-lines-in-the-browser-numbers-case-and-the-whitespace-that-decides-the-order-3kg2</link>
      <guid>https://dev.to/onepassapps/sort-lines-in-the-browser-numbers-case-and-the-whitespace-that-decides-the-order-3kg2</guid>
      <description>&lt;p&gt;I work on OnePassApps. We build browser studios that do their work in the tab instead of uploading your files, and Text Studio is the one for plain text: eight tools on one tab bar - Clean, Case, Duplicate lines, Sort lines, Find &amp;amp; replace, Diff, Markdown and Base64.&lt;/p&gt;

&lt;p&gt;The Sort lines tab is the one that looks trivial until you feed it a list that is not a plain list of names. Version strings, IDs typed as text, a column of numbers - each of those has a different idea of what "sorted" means. So this is about the ordering rules rather than the button: what the three options do, and what the tool does with spaces, blank lines and duplicates.&lt;/p&gt;

&lt;p&gt;The page describes itself in one line: "Count, clean, compare and convert text locally. Your content stays in this browser tab."&lt;/p&gt;

&lt;h2&gt;
  
  
  What the tab gives you
&lt;/h2&gt;

&lt;p&gt;One panel, three steps. &lt;strong&gt;01 Input text&lt;/strong&gt; - a box with a live counter (2,000,000 characters on this tab) and a live stats row: Words, Characters, Sentences, Lines and UTF-8 bytes. &lt;strong&gt;02 Options&lt;/strong&gt; - three controls: Sort order (A to Z or Z to A), Case-sensitive and Natural number order. &lt;strong&gt;03 Result&lt;/strong&gt; - "Sorted lines shows on the stage, ready to copy, download or use as the new input."&lt;/p&gt;

&lt;p&gt;The action button reads "Sort lines" and becomes "Sort again" once a run has happened. The result panel then reports the size of what it produced and how long it took ("Sorted in 0.2 s"), and offers Use as input, Download .txt and Copy. The stage footer reads "Text transformed locally." and the panel says "Nothing leaves this tab."&lt;/p&gt;

&lt;p&gt;You can drop a file in instead of pasting: the drop area takes ".txt, .md, .csv and other text up to 8 MiB", and the tab says "Processing stays in this browser."&lt;/p&gt;

&lt;h2&gt;
  
  
  Run 1 - what the defaults do with numbers
&lt;/h2&gt;

&lt;p&gt;Input (6 lines, 33 characters - the stats row read Words 7, Characters 33, Sentences 0, Lines 6, UTF-8 bytes 33):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;banana
Apple
10
9
cherry
2 apples
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Options left where they ship: Sort order A to Z, Case-sensitive off, Natural number order on. Result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2 apples
9
10
Apple
banana
cherry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things happened. The numbers came first and in numeric order - 9 before 10, not "10" before "9" the way a plain text sort does it. And "2 apples", which starts with a digit, was treated as a number and sorted into that group: 2, then 9, then 10. Everything that does not start with a digit came after, alphabetically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run 2 - the same input with Natural number order switched off
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10
2 apples
9
Apple
banana
cherry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is plain lexicographic order: "10" first because the character "1" comes before "2", and "2 apples" before "9". If your list is numbers stored as text, this is the difference between a useful order and a broken one. Natural number order is on by default, which is the right default for a list of IDs, and the one to turn off for version strings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run 3 - Case-sensitive, and what the toggle actually changes
&lt;/h2&gt;

&lt;p&gt;The checkbox sits under Sort order and ships off. Here is the same input sorted twice.&lt;/p&gt;

&lt;p&gt;Input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Zebra
apple
Apple
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Case-sensitive off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apple
Apple
Zebra
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Case-sensitive on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apple
Apple
Zebra
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same output - because the only case difference in this sample is between "apple" and "Apple", and with the comparison turned off those two compare equal, so their original order is kept. Here is a sample where the toggle does change the answer:&lt;/p&gt;

&lt;p&gt;Input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apple
Banana
banana
apple
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Case-sensitive off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apple
apple
Banana
banana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Case-sensitive on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apple
apple
banana
Banana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now "Banana" and "banana" are no longer tied. The lower-case one came first. So the toggle is not "upper case before lower case" - it stops treating two spellings as the same value, and the order it then picks put the lower-case spelling first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run 4 - whitespace, blank lines and duplicates are left alone
&lt;/h2&gt;

&lt;p&gt;Input (9 lines, 46 characters) - note the leading spaces on the first line, the two empty lines, the trailing spaces after "trailing", the repeated "line", and both spellings of "lead":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  lead
line


second
trailing  
line
line
Lead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

  lead
Lead
line
line
line
second
trailing  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four things to notice. The empty lines did not disappear - they sorted to the top, because an empty string sorts before any letter, and they are still in the output. The leading spaces on "  lead" are kept, and they are why that line sorts above "Lead": a space sorts before a letter. The trailing spaces on "trailing" are kept in the output too. And the three "line" entries are all still there - Sort lines reorders, it never de-duplicates; removing duplicates is the separate Duplicate lines tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bit that will bite you
&lt;/h2&gt;

&lt;p&gt;Sorting is a comparison, and the comparison is not your editor's. Two things to watch. Whitespace is part of the value unless you clean it first - the Clean tab removes trailing spaces, trims outer whitespace and collapses repeated blank lines, so run it first on a list that came out of a copy-paste. And lines that compare equal keep their original order instead of being shuffled, which is why the two "apple" lines stay in place in the run above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it runs
&lt;/h2&gt;

&lt;p&gt;Text Studio is part of OnePassApps and it lives on the &lt;a href="https://app.onepassapps.com/studios/text?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=text-sort-lines&amp;amp;utm_content=text-studio-link" rel="noopener noreferrer"&gt;Text Studio page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The whole thing happens in the tab: the file is read in the browser, the lines are compared there, and the sorted copy is written out on your own machine. Standard is free right now - $0/month, 100,000 credits every 30 days, no card required, and no automatic paid subscription.&lt;/p&gt;

&lt;p&gt;Guides for the studios and the apps behind them are on the &lt;a href="https://app.onepassapps.com/guides?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=text-sort-lines&amp;amp;utm_content=guides-link" rel="noopener noreferrer"&gt;OnePassApps guides page&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>text</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>tools</category>
    </item>
    <item>
      <title>How the OnePassApps Vote Board Counts: Ties, Ordering and the Sign-In Gate</title>
      <dc:creator>OnePassApps</dc:creator>
      <pubDate>Tue, 03 Nov 2026 03:28:23 +0000</pubDate>
      <link>https://dev.to/onepassapps/how-the-onepassapps-vote-board-counts-ties-ordering-and-the-sign-in-gate-54nl</link>
      <guid>https://dev.to/onepassapps/how-the-onepassapps-vote-board-counts-ties-ordering-and-the-sign-in-gate-54nl</guid>
      <description>&lt;p&gt;Most "tell us what to build next" widgets are a form in a corner with a text box under it. The OnePassApps voting board is a different shape of thing: a live board with five ideas on it, a vote count on every card, and a stated rule for what rises to the top.&lt;/p&gt;

&lt;p&gt;I work on OnePassApps, the team behind app.onepassapps.com, so I am going to describe how the board actually behaves - the ordering rule, the refresh, the sign-in gate - and exactly what each of the five ideas says it will do.&lt;/p&gt;

&lt;p&gt;Open the &lt;a href="https://app.onepassapps.com/voting?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=community-vote-board&amp;amp;utm_content=vote-board-link" rel="noopener noreferrer"&gt;community voting board&lt;/a&gt; to look at it yourself, or read the &lt;a href="https://app.onepassapps.com/guides?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=community-vote-board&amp;amp;utm_content=guides-link" rel="noopener noreferrer"&gt;app guides&lt;/a&gt; for the tools that are already live.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three steps the board spells out
&lt;/h2&gt;

&lt;p&gt;The panel above the cards is a short explainer, and it is worth reading because it tells you what the page is asking of you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;01 Browse ideas&lt;/strong&gt; - "Find the apps you'd use"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;02 Use your votes&lt;/strong&gt; - "Back your favourites"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;03 Set priorities&lt;/strong&gt; - "Top ideas rise first"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That third line is the ordering rule in plain words. The board is not a poll where every idea is fixed in place; support is what moves a card.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rules the board prints about itself
&lt;/h2&gt;

&lt;p&gt;Four short strings on the page do the real explaining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"5 ideas" - the count of cards currently on the board.&lt;/li&gt;
&lt;li&gt;"Updates every 30 seconds" - the refresh interval for the vote counts, so you can leave the page open and watch a card climb without reloading.&lt;/li&gt;
&lt;li&gt;"TIED AT 0 VOTES" - the tie marker, with the tiebreak spelled out next to it: "IN THE ORDER ADDED". When nothing has any support yet, the board does not hide that fact; it shows the tie and then breaks it by insertion order, so the list you see is stable rather than arbitrary.&lt;/li&gt;
&lt;li&gt;"Top ideas rise first" - the sort rule that follows from the votes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also a "YOUR VOTES" panel with the line "Back an idea, or bring your own", and the count of ideas on the board sits next to the label "Ideas in the running".&lt;/p&gt;

&lt;h2&gt;
  
  
  Voting needs an account, and that is deliberate
&lt;/h2&gt;

&lt;p&gt;A visitor can browse the board. Voting does not work for a visitor - the page says "Sign in to vote for the ideas you'd use. Pro also lets you submit an app idea of your own", and the button on the cards reads "Sign in to vote".&lt;/p&gt;

&lt;p&gt;That gate is the point of the board rather than an obstacle to it. A vote is only useful if it is tied to a person, otherwise the ordering rule is trivially gameable. The same panel explains the second half of the loop: if none of the five is the thing you would use, the board takes a new idea. At the bottom of the list there is a "Have an idea?" block with a "Suggest an app" button.&lt;/p&gt;

&lt;p&gt;So the loop is: browse the running ideas, back the ones you would actually use, and if the board has nothing for you, put your own idea on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five ideas on the board right now
&lt;/h2&gt;

&lt;p&gt;These are the cards currently on the board, with the descriptions the board gives them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;ReadAloud&lt;/strong&gt; - "A dedicated app for listening to supplied text with clear playback and reading controls."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SubRadar&lt;/strong&gt; - "Track recurring subscriptions in one place, spot overlapping AI tools, and get clear suggestions about what to cancel or keep."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DumpList&lt;/strong&gt; - "Turn a quick voice dump into structured tasks, due dates, and optional calendar blocks."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NoteQuiz&lt;/strong&gt; - "Turn a photo, handwritten page, or PDF into flashcards, a ten-question quiz, and a recap of what you keep missing."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FiveMin&lt;/strong&gt; - "Pick topics and creators, then get a concise daily briefing written in your tone and ready to play as audio."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these five is a shipped app. They are the running list, and the board is where you would push back on them - a vote for ReadAloud and a suggestion of your own thing both end up in the same place, and the ordering rule decides which idea gets built first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is actually live today
&lt;/h2&gt;

&lt;p&gt;So the board is not the whole product, and I would rather say what exists than let the board imply there is nothing else. Nine apps are live now under one membership, and Standard is free at the moment with no card needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MovieMatcherAI&lt;/strong&gt; - browse, rate and keep a watchlist. The Browse tab lists 89 titles, with filters for new releases, top rated, rating, genre, year and person; ratings and watchlist sync to an account, so signing in is what makes the list yours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NewsFlow&lt;/strong&gt; - server-side news briefs with source attribution and a link back to the original.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seven browser Studios&lt;/strong&gt; - Image, Audio, File, Text, Data, Icon and Colour. They process the file in the tab instead of uploading it, which is the same idea as the vote board in a different place: the thing happens where the person is, and the file never leaves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Studios are the part most people come for, and they are the part I have written most about. The voting board is the part I would point a developer at, because the interesting detail is in the rules: a tie shown honestly instead of hidden, a stated tiebreak, a refresh interval you can plan around, and a sign-in requirement that makes the ordering mean something.&lt;/p&gt;

&lt;p&gt;If one of the five is the app you would use, back it. If it is not, put your own idea on the board - the "Suggest an app" button is right there under the list.&lt;/p&gt;

&lt;p&gt;Disclosure again: I work on OnePassApps, the team behind app.onepassapps.com.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tools</category>
      <category>productivity</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Markdown Preview in the Browser: What the Sanitiser Drops Before You Copy</title>
      <dc:creator>OnePassApps</dc:creator>
      <pubDate>Tue, 03 Nov 2026 03:00:25 +0000</pubDate>
      <link>https://dev.to/onepassapps/markdown-preview-in-the-browser-what-the-sanitiser-drops-before-you-copy-21lo</link>
      <guid>https://dev.to/onepassapps/markdown-preview-in-the-browser-what-the-sanitiser-drops-before-you-copy-21lo</guid>
      <description>&lt;p&gt;Most "markdown preview" boxes on the web do one of two things: they render your Markdown and then quietly fetch every image and follow every link, or they escape the raw HTML and hand you a wall of &lt;code&gt;&amp;amp;lt;&lt;/code&gt; characters. OnePassApps' Text Studio takes a third route: it renders the Markdown, strips the dangerous parts, and shows you exactly what is left before you copy it.&lt;/p&gt;

&lt;p&gt;I wanted to know what survives that strip, so I ran the tab against a file containing a heading, emphasis, a table, a fenced code block, a link, an image, a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag and an inline &lt;code&gt;&amp;lt;b onclick=...&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Disclosure: I work on OnePassApps.&lt;/p&gt;

&lt;p&gt;Open the &lt;a href="https://app.onepassapps.com/studios/text?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=markdown-sanitiser&amp;amp;utm_content=text-studio-link" rel="noopener noreferrer"&gt;Text Studio&lt;/a&gt; to try it, or read the &lt;a href="https://app.onepassapps.com/guides?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=markdown-sanitiser&amp;amp;utm_content=guides-link" rel="noopener noreferrer"&gt;app guides&lt;/a&gt; for the rest of the tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tab, and its smaller input limit
&lt;/h2&gt;

&lt;p&gt;Text Studio has eight tools in one tab strip: Clean, Case, Duplicate lines, Sort lines, Find &amp;amp; replace, Diff, Markdown and Base64. The Markdown tab is the eighth.&lt;/p&gt;

&lt;p&gt;The page line over the strip reads "Count, clean, compare and convert text locally. Your content stays in this browser tab." The file picker accepts ".txt, .md, .csv and other text up to 8 MiB · or paste it in Input text · nothing is uploaded", and the page repeats "Nothing leaves this tab." and "Processing stays in this browser."&lt;/p&gt;

&lt;p&gt;One detail worth knowing before you paste: the input counter on the Markdown tab reads &lt;strong&gt;250,000&lt;/strong&gt;, not 2,000,000. The Clean tab's counter reads 2,000,000. So the Markdown tab has a lower ceiling than its neighbours, and it is the tab where a ceiling matters, because a rendered preview is a heavier operation than a line-ending fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The panel says what it does before you press anything
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;01 Input text&lt;/strong&gt; — the counter, plus a live Words / Characters / Sentences / Lines / UTF-8 bytes row.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;02 Preview&lt;/strong&gt; — "Markdown renders in this tab without opening links or loading images and scripts. Copy the sanitised HTML when it looks right."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;03 Result&lt;/strong&gt; — "The preview is sanitised: links, images and scripts are never loaded."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the contract, and it is worth reading carefully because the middle sentence is narrower than it looks. "Links" are not blocked. Links are &lt;em&gt;stripped of their targets&lt;/em&gt;. That is a different thing, and the difference shows up in the output.&lt;/p&gt;

&lt;p&gt;The action button reads "Preview Markdown". After a run it becomes "Preview again", and the result panel reports "Ready" and "Rendered in" a fraction of a second, with a "Copy HTML" button and a line under the heading: "Safe preview · links, images and scripts are not loaded".&lt;/p&gt;

&lt;h2&gt;
  
  
  The input I used
&lt;/h2&gt;

&lt;p&gt;Nothing exotic — one heading, one paragraph, a list, a table, a code fence, a link, an image, a script tag and an inline HTML tag with an event handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Heading one&lt;/span&gt;

A paragraph with &lt;span class="gs"&gt;**bold**&lt;/span&gt;, &lt;span class="ge"&gt;*italic*&lt;/span&gt;, &lt;span class="sb"&gt;`inline code`&lt;/span&gt; and a &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;link&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://example.com&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;.
&lt;span class="p"&gt;
-&lt;/span&gt; first item
&lt;span class="p"&gt;-&lt;/span&gt; second item

| name | role |
| --- | --- |
| Ada | engineer |

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;js
&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;```&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;alert('xss')&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;b&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"alert(1)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;raw html&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;alt text&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://example.com/a.png&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The counter read 322 characters, 48 words, 20 lines and 322 UTF-8 bytes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What came out
&lt;/h2&gt;

&lt;p&gt;Here is the markup that "Copy HTML" put on the clipboard, 427 characters, exactly as it was written:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Heading one&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;A paragraph with &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&lt;/span&gt;bold&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;, &lt;span class="nt"&gt;&amp;lt;em&amp;gt;&lt;/span&gt;italic&lt;span class="nt"&gt;&amp;lt;/em&amp;gt;&lt;/span&gt;, &lt;span class="nt"&gt;&amp;lt;code&amp;gt;&lt;/span&gt;inline code&lt;span class="nt"&gt;&amp;lt;/code&amp;gt;&lt;/span&gt; and a &lt;span class="nt"&gt;&amp;lt;a&amp;gt;&lt;/span&gt;link&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;first item&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;second item&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;name&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;role&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;tbody&amp;gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;Ada&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;engineer&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;pre&amp;gt;&amp;lt;code&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"language-js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;const x = 1 &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt; 2 &lt;span class="ni"&gt;&amp;amp;amp;&amp;amp;amp;&lt;/span&gt; 3 &lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt; 2;
&lt;span class="nt"&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;b&amp;gt;&lt;/span&gt;raw html&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The button confirms it with "Sanitised HTML copied to the clipboard." and the visible preview is the same markup, rendered in the page under the label "Safe preview · links, images and scripts are not loaded".&lt;/p&gt;

&lt;p&gt;Read that block line by line, because each line is a different rule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Headings, emphasis, lists, tables and code fences all survive, intact.&lt;/strong&gt; The table is real table markup, with a thead row and a tbody row. The fenced code block keeps its language class — the opening tag carries the class "language-js" — and the code inside is HTML-escaped the way it should be, so &lt;code&gt;1 &amp;lt; 2 &amp;amp;&amp;amp; 3 &amp;gt; 2&lt;/code&gt; comes out as &lt;code&gt;1 &amp;amp;lt; 2 &amp;amp;amp;&amp;amp;amp; 3 &amp;amp;gt; 2&lt;/code&gt; instead of being parsed as markup. That escaping is why the code block is safe to paste into a page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The script tag is gone.&lt;/strong&gt; Not escaped, not shown as text — gone. There is no script element in the output and no trace of the alert call. The one line it occupied is simply absent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The link keeps its text and loses its target.&lt;/strong&gt; The anchor element is still there, still wrapping the word "link" — but it has no href attribute at all. It is not a link to &lt;a href="https://example.com" rel="noopener noreferrer"&gt;https://example.com&lt;/a&gt; any more; it is a piece of text that used to be a link. That is what "links are not loaded" means in practice, and it is the difference between a preview that is safe to paste and one that quietly carries live URLs out of the page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The image disappears entirely.&lt;/strong&gt; The markdown image produced no image element and no alt text — just an empty paragraph. So there is nothing to load, and the stripped line leaves a blank paragraph behind it. Worth knowing if you diff the result against your source: an image line becomes an empty paragraph, not a removed line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raw HTML is kept but stripped of its attributes.&lt;/strong&gt; The inline bold tag came through as a bold element with the text "raw html" in it, and the onclick handler was dropped. So the sanitiser does not escape all HTML — it filters it. Tags with dangerous attributes lose those attributes; the safe parts of the tag survive.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for pasting the output
&lt;/h2&gt;

&lt;p&gt;The copy is a fragment, not a full document — no &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;, no &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, no body wrapper. It starts at the first heading. That is what you want if you are pasting into a CMS, a README, an email composer or a comment box: markup, with the executable parts removed and the loadable parts removed.&lt;/p&gt;

&lt;p&gt;It is also not a round trip. Markdown in does not mean Markdown out — what you get is HTML, and the image is the clearest example of a source line that has no counterpart in the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;The plan block on the page reads: "FREE PROMOTIONAL PERIOD", "Standard is free right now", "$0 / month", "100,000 credits every 30 days · No end date set · No card required" and "No automatic paid subscription. Existing paid subscriptions keep their terms."&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;p&gt;If you write Markdown in a tool and paste it into something else, the usual worry is that the paste carries a script, a tracking pixel, or a link that fires a request when someone opens it. This tab answers that worry by removing the targets rather than trusting them, and by showing you the exact output before you copy it — the copy button and the preview are the same bytes.&lt;/p&gt;

&lt;p&gt;It does not replace a linter or a sanitiser for content you did not write, and it does not render anything to a file: the preview lives in the tab, and "Copy HTML" is the hand-off.&lt;/p&gt;

&lt;p&gt;The rest of the Text Studio sits next to it: Clean, Case, Duplicate lines, Sort lines, Find &amp;amp; replace, Diff and Base64.&lt;/p&gt;

&lt;p&gt;Disclosure again: I work on OnePassApps, the team behind app.onepassapps.com.&lt;/p&gt;

</description>
      <category>markdown</category>
      <category>webdev</category>
      <category>security</category>
      <category>tools</category>
    </item>
    <item>
      <title>Batch Rename Files in the Browser: The Preview Before the ZIP</title>
      <dc:creator>OnePassApps</dc:creator>
      <pubDate>Tue, 03 Nov 2026 02:08:49 +0000</pubDate>
      <link>https://dev.to/onepassapps/batch-rename-files-in-the-browser-the-preview-before-the-zip-28bp</link>
      <guid>https://dev.to/onepassapps/batch-rename-files-in-the-browser-the-preview-before-the-zip-28bp</guid>
      <description>&lt;p&gt;Rename a folder of 200 files and you find out three things fast: the naming convention you half-remember, the two names that would collide, and the fact that most web-based rename tools want the whole folder before they show you a single new name.&lt;/p&gt;

&lt;p&gt;The File Studio's &lt;strong&gt;Batch rename&lt;/strong&gt; tab in OnePassApps does the opposite. You drop the files in, type the rule, and a rename preview tells you exactly what each name becomes before anything is packed.&lt;/p&gt;

&lt;p&gt;Disclosure: I work on OnePassApps.&lt;/p&gt;

&lt;p&gt;Open the &lt;a href="https://app.onepassapps.com/studios/files?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=batch-rename-preview-launch&amp;amp;utm_content=file-studio-link" rel="noopener noreferrer"&gt;File Studio&lt;/a&gt; to try it, or read the &lt;a href="https://app.onepassapps.com/guides?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=batch-rename-preview-launch&amp;amp;utm_content=guides-link" rel="noopener noreferrer"&gt;app guides&lt;/a&gt; for the rest of the tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;01 Source&lt;/strong&gt; — the files you picked. The counter starts at "0 files · 0 B." and the panel tells you what will come back: "Renamed copies are packed in one ZIP."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;02 New names&lt;/strong&gt; — Prefix, Suffix before extension, Find in filename, Replace with, a Case conversion control, an "Add sequence number" checkbox and a "Starts at" field. The hint under the numbering is "Turn on numbering first."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;03 Result&lt;/strong&gt; — "Create renamed copies ZIP packs the new names into renamed-copies.zip. Names on your device never change."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last sentence is the whole design. It makes copies; it does not touch your disk. The drop area says "Up to 500 files and 256 MiB · the names on your device never change", and the page repeats the two lines this Studio is built on: "Nothing leaves this tab." and "Processing stays in this browser."&lt;/p&gt;

&lt;h2&gt;
  
  
  What the rename preview actually shows
&lt;/h2&gt;

&lt;p&gt;The preview panel is headed RENAME PREVIEW and starts empty ("Nothing chosen yet"). Once files are loaded it lists, per file, the old name, the new name and the size.&lt;/p&gt;

&lt;p&gt;Here is a run I did on two real files, "Q3 Report.txt" and "notes.txt", five bytes each. I typed &lt;code&gt;2026-&lt;/code&gt; into the Prefix field and ticked "Add sequence number". The preview read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2 files&lt;/li&gt;
&lt;li&gt;2 names change&lt;/li&gt;
&lt;li&gt;Q3 Report.txt → 2026-Q3 Report-1.txt&lt;/li&gt;
&lt;li&gt;notes.txt → 2026-notes-2.txt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Look at where the number lands: it is inserted before the extension, and it counts across the whole selection in order, so the second file gets 2 even though the sequence started at 1.&lt;/p&gt;

&lt;h2&gt;
  
  
  The collision rule
&lt;/h2&gt;

&lt;p&gt;Rename rules collide. Two different files can end up with the same new name, and whatever builds the archive has to resolve that or one file silently disappears.&lt;/p&gt;

&lt;p&gt;The panel states the rule in one line: "Name collisions are resolved in selection order with “(2)”, “(3)” and so on."&lt;/p&gt;

&lt;p&gt;So a name that is already taken in the pack gets (2) appended, then (3), and the order comes from the order you selected the files — not from alphabetical order. Selection order is the thing to control here, because that is what decides which file keeps the clean name and which one gets the suffix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The other two name controls
&lt;/h2&gt;

&lt;p&gt;Two more controls sit in step 02, and both compose with the prefix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Find in filename&lt;/strong&gt; / &lt;strong&gt;Replace with&lt;/strong&gt; — a search-and-replace applied to the name, not to the contents. Fill them in and the preview updates as you go.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Case conversion&lt;/strong&gt; — a control that opens on "Keep as-is".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The preview is the only place the combined result shows, so it is worth reading every line of it before you pack anything. Two rules that each look fine on their own can produce a name you did not want.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get back
&lt;/h2&gt;

&lt;p&gt;The action button reads "Create renamed copies ZIP", and the panel explains the promise: "Create renamed copies ZIP packs the new names into renamed-copies.zip. Names on your device never change."&lt;/p&gt;

&lt;p&gt;One ZIP with the new names inside, and the originals still exactly where they were. Nothing was renamed in place, so if the preview is wrong you have changed nothing and there is nothing to undo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;p&gt;If the job is "rename these 300 screenshots by date and number", this tab is quicker than a shell script and it needs no terminal. If the job is "rename the folder itself" or "rename in place", it will not do that — the tool deliberately makes copies. And if the renamed files need to go somewhere else, the ZIP is the hand-off, because there is no upload step and no server step.&lt;/p&gt;

&lt;p&gt;The rest of the File Studio sits next to it: ZIP files, Unzip files, File hash, Find duplicates, Split file and Join parts.&lt;/p&gt;

&lt;p&gt;Disclosure again: I work on OnePassApps, the team behind app.onepassapps.com.&lt;/p&gt;

</description>
      <category>file</category>
      <category>tools</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Merge Two CSVs in the Browser: Rows Align by Header Name, Not Column Order</title>
      <dc:creator>OnePassApps</dc:creator>
      <pubDate>Tue, 03 Nov 2026 01:27:02 +0000</pubDate>
      <link>https://dev.to/onepassapps/merge-two-csvs-in-the-browser-rows-align-by-header-name-not-column-order-83c</link>
      <guid>https://dev.to/onepassapps/merge-two-csvs-in-the-browser-rows-align-by-header-name-not-column-order-83c</guid>
      <description>&lt;p&gt;Two exports, the same information, slightly different columns, and a tool that wants you to fix the headers by hand before it will combine anything. That last part is usually where I stop using online merge tools.&lt;/p&gt;

&lt;p&gt;The Data Studio's &lt;strong&gt;Merge&lt;/strong&gt; tab in OnePassApps does the matching for you, in the browser tab. The files are read, joined and handed back to you on the same machine — the panel says so plainly: "Nothing leaves this tab." and "Processing stays in this browser."&lt;/p&gt;

&lt;p&gt;Open the &lt;a href="https://app.onepassapps.com/studios/data?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=merge-csv-launch&amp;amp;utm_content=data-studio-link" rel="noopener noreferrer"&gt;Data Studio&lt;/a&gt; to try it, or read the &lt;a href="https://app.onepassapps.com/guides?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=merge-csv-launch&amp;amp;utm_content=guides-link" rel="noopener noreferrer"&gt;app guides&lt;/a&gt; for the rest of the tools.&lt;/p&gt;

&lt;p&gt;Disclosure: I work on OnePassApps.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the merge is laid out
&lt;/h2&gt;

&lt;p&gt;The tab is four numbered steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;01 Source&lt;/strong&gt; — the first file, in a field called "First CSV input"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;02 Second CSV&lt;/strong&gt; — the second file, in "Second CSV input"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;03 Options&lt;/strong&gt; — a "Delimiter" picker and the rule that governs the whole tool&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;04 Result&lt;/strong&gt; — "Rows, columns and any parsing issues appear here after the tool runs."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule is the part worth reading twice: "Both files need the same unique headers. Column order may differ; rows are aligned by header name."&lt;/p&gt;

&lt;p&gt;That second sentence is the useful one. Your two files do not have to list their columns in the same order.&lt;/p&gt;

&lt;p&gt;The action button is "Merge compatible CSVs". The delimiter picker defaults to "Auto detect" and offers Comma, Tab, Semicolon and Pipe; on comma-separated input it reported "Delimiter found Comma".&lt;/p&gt;

&lt;h2&gt;
  
  
  A run where the columns are in a different order
&lt;/h2&gt;

&lt;p&gt;First file — name, role, city:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name,role,city
Ada,engineer,Berlin
Grace,engineer,London
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second file — city, name, role:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;city,name,role
Helsinki,Linus,maintainer
Berlin,Ada,engineer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After "Merge compatible CSVs" the result panel read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Merged 2 + 2 compatible rows."&lt;/li&gt;
&lt;li&gt;"4 rows · 3 columns · no parsing issues"&lt;/li&gt;
&lt;li&gt;"Delimiter found Comma"&lt;/li&gt;
&lt;li&gt;"Merged in 447 ms"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The preview listed the four merged rows with a detected type on every column — name TEXT, role TEXT, city TEXT — and the exported file kept the &lt;strong&gt;first&lt;/strong&gt; file's column order: name, role, city. The downloaded file was a plain 136-byte CSV with every value quoted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"name","role","city"
"Ada","engineer","Berlin"
"Grace","engineer","London"
"Linus","maintainer","Helsinki"
"Ada","engineer","Berlin"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second file's city/name/role order was resolved by name, and the output follows the first file's header order. So a column-order difference is not a reason to reach for a spreadsheet first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when the headers do not match
&lt;/h2&gt;

&lt;p&gt;Now the interesting part, because "merge two CSVs" is usually a request and sometimes a trap. Rename one header in the second file — city becomes country — and the run stops:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Could not use this data"&lt;/li&gt;
&lt;li&gt;"CSV schemas do not match."&lt;/li&gt;
&lt;li&gt;"Second CSV is missing: city."&lt;/li&gt;
&lt;li&gt;"Second CSV has extra: country."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The message names both sides of the difference: the header the second file should have had, and the one it brought instead. The preview area says "Nothing to show yet" and "Fix the problem shown above the run action, then run the tool again."&lt;/p&gt;

&lt;p&gt;Now try the other direction — keep all three headers and add a fourth, id — and the run is refused as well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"CSV schemas do not match."&lt;/li&gt;
&lt;li&gt;"Second CSV has extra: id."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That second case is the important one. Every other header matched, and the tool still refused the whole run instead of merging the three columns it could have paired and quietly dropping the fourth. If you wanted a partial join, this is not it; if you wanted to know exactly which column is in the way, this is exactly it.&lt;/p&gt;

&lt;h2&gt;
  
  
  It stacks, it does not de-duplicate
&lt;/h2&gt;

&lt;p&gt;One more detail you will want before you rely on the output. The merge is a concatenation, not a cleanup. In the run above, "Ada" appears twice in the merged file — once from each input — and the output really does contain four rows for two files of two rows each. "Merged 2 + 2 compatible rows." is literal.&lt;/p&gt;

&lt;p&gt;If you also need the duplicate rows gone, that is a different tab in the same studio, and it reports how many rows it removed instead of folding them together.&lt;/p&gt;

&lt;h2&gt;
  
  
  The limits, stated plainly
&lt;/h2&gt;

&lt;p&gt;The studio's source area takes "Drop a data file, or browse CSV, TSV, JSON or YAML · up to 20 MiB · or paste rows in Source". The merge tool itself is a CSV tool: its two fields are described as "Paste the first CSV or drop a file…" and "Paste a CSV with the same headers…", and the button reads "Merge compatible CSVs".&lt;/p&gt;

&lt;p&gt;The plan line on the page reads "FREE PROMOTIONAL PERIOD", "Standard is free right now", "$0 / month", "100,000 credits every 30 days · No end date set · No card required", and "No automatic paid subscription. Existing paid subscriptions keep their terms."&lt;/p&gt;

&lt;h2&gt;
  
  
  One tab of eleven
&lt;/h2&gt;

&lt;p&gt;The Data Studio carries eleven tools on the same page: CSV Viewer, Sort / filter, Columns, Duplicates, Merge, Split, To JSON, JSON Formatter, Validator, To CSV and YAML. They all read the same kind of local data and none of them sends it anywhere, which makes them usable together — sort and filter first, merge two exports second, drop the duplicate rows third.&lt;/p&gt;

&lt;p&gt;If the CSV you are staring at is sensitive, that is the part worth caring about: the work happens in the tab you already have open, and the file never travels to a server that has to be trusted with it.&lt;/p&gt;

</description>
      <category>csv</category>
      <category>data</category>
      <category>tools</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Extract Audio From a Video in the Browser: WAV or WebM Opus, Checked Byte by Byte</title>
      <dc:creator>OnePassApps</dc:creator>
      <pubDate>Tue, 03 Nov 2026 01:09:33 +0000</pubDate>
      <link>https://dev.to/onepassapps/extract-audio-from-a-video-in-the-browser-wav-or-webm-opus-checked-byte-by-byte-4m95</link>
      <guid>https://dev.to/onepassapps/extract-audio-from-a-video-in-the-browser-wav-or-webm-opus-checked-byte-by-byte-4m95</guid>
      <description>&lt;p&gt;Pulling the sound out of a video usually means uploading the file to somebody's server. There is a&lt;br&gt;
second way: decode the audio track in the tab you already have open, and write the result to your&lt;br&gt;
own disk.&lt;/p&gt;

&lt;p&gt;I built a small MP4 to test this with - a six second clip with a 440 Hz tone in it - and ran it&lt;br&gt;
through the Extract tab of the Audio Studio at &lt;a href="https://app.onepassapps.com/studios/audio?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=audio-extract-format&amp;amp;utm_content=audio-studio-link" rel="noopener noreferrer"&gt;app.onepassapps.com/studios/audio&lt;/a&gt;.&lt;br&gt;
I work on OnePassApps, so this is our tool and our test.&lt;/p&gt;

&lt;p&gt;The headline: the file never leaves the tab, and you get a real audio file, not a preview.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the panel actually offers
&lt;/h2&gt;

&lt;p&gt;The tab is called &lt;strong&gt;Extract&lt;/strong&gt;, and it has three steps. Before you load anything, step 01 reads:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No video yet. Drop one on the stage or browse for it. Up to 32 MiB and 45 minutes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Step 02 is where the interesting choices live, and they only appear once a video has been checked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Format, sample rate and channels appear here once the video has been checked.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The page itself sets the boundary - the file hint reads "MP4, MOV, MKV, WebM or MPEG-TS with an&lt;br&gt;
audio track · up to 32 MiB and 45 minutes" - and the page line says "Convert, compress, trim and&lt;br&gt;
extract audio locally. Your media stays in this browser tab."&lt;/p&gt;

&lt;p&gt;Load a video and three dropdowns appear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Output format&lt;/strong&gt;: &lt;code&gt;WAV audio (PCM)&lt;/code&gt; or &lt;code&gt;WebM audio (Opus)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample rate&lt;/strong&gt;: &lt;code&gt;48,000 Hz&lt;/code&gt;, &lt;code&gt;44,100 Hz&lt;/code&gt;, &lt;code&gt;32,000 Hz&lt;/code&gt;, &lt;code&gt;24,000 Hz&lt;/code&gt;, &lt;code&gt;16,000 Hz&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Channels&lt;/strong&gt;: for a mono source, &lt;code&gt;Mono&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a note under them: "Defaults keep the source’s mono and sample rate when the chosen&lt;br&gt;
encoder supports them." In practice that means the defaults followed my file - a 44.1 kHz mono&lt;br&gt;
source came back as &lt;code&gt;44,100 Hz&lt;/code&gt; and &lt;code&gt;Mono&lt;/code&gt; without me touching anything.&lt;/p&gt;

&lt;p&gt;One line in step 01 tells you what you are about to get, and it matters:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Only the primary audio track is exported. Video and other tracks are discarded locally.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So this is an audio extraction, not a repackage of the whole file.&lt;/p&gt;
&lt;h2&gt;
  
  
  Run 1: the defaults
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;probe-extract.mp4&lt;/code&gt; on the stage, the panel showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MP4 · 0:06.0 · 98.7 KiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the stage header confirmed what was inside it before I touched anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Container     MP4
Audio codec   AAC
Sample rate   44,100 Hz
Channels      Mono
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pressing &lt;strong&gt;Extract audio&lt;/strong&gt; had produced the file within three seconds and the result panel read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OUTPUT
probe-extract-audio.wav
0:06.0 · 519 KiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with &lt;code&gt;Format WAV&lt;/code&gt;, &lt;code&gt;Sample rate 44,100 Hz&lt;/code&gt;, &lt;code&gt;Channels Mono&lt;/code&gt;, &lt;code&gt;Size 519 KiB&lt;/code&gt;, and the line that&lt;br&gt;
tells you nothing went anywhere else:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Output created locally. Your original file remains unchanged.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The output file name is derived from the source - &lt;code&gt;probe-extract.mp4&lt;/code&gt; became&lt;br&gt;
&lt;code&gt;probe-extract-audio.wav&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The plan block on the page reads "Standard is free right now" and "$0 / month" with "No card required", and the run above was done in a tab that was not signed in.&lt;/p&gt;
&lt;h2&gt;
  
  
  Read the bytes back, do not trust the label
&lt;/h2&gt;

&lt;p&gt;A panel that says "WAV" and a panel that says "MP3" are both just text. So I pulled the actual&lt;br&gt;
downloaded blob off the page and parsed it with &lt;code&gt;ffprobe&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;codec_name&lt;/span&gt;=&lt;span class="n"&gt;pcm_s16le&lt;/span&gt;
&lt;span class="n"&gt;sample_rate&lt;/span&gt;=&lt;span class="m"&gt;44100&lt;/span&gt;
&lt;span class="n"&gt;channels&lt;/span&gt;=&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;duration&lt;/span&gt;=&lt;span class="m"&gt;6&lt;/span&gt;.&lt;span class="m"&gt;023220&lt;/span&gt;
&lt;span class="n"&gt;format_name&lt;/span&gt;=&lt;span class="n"&gt;wav&lt;/span&gt;
&lt;span class="n"&gt;size&lt;/span&gt;=&lt;span class="m"&gt;531292&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a genuine RIFF/WAVE file - the first bytes are &lt;code&gt;RIFF&lt;/code&gt; then &lt;code&gt;WAVE&lt;/code&gt; then &lt;code&gt;fmt&lt;/code&gt; - with&lt;br&gt;
16-bit signed little-endian PCM, one channel, at the rate the source had.&lt;/p&gt;

&lt;p&gt;The better test is the content. My test video carried a 440 Hz sine tone, so if the extractor really&lt;br&gt;
decoded the track, the WAV should contain a 440 Hz waveform. Counting zero crossings over one&lt;br&gt;
second of the extracted audio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;frames 265624  channels 1  sampwidth 2  rate 44100  dur 6.023
peak 6204  rms 2886.6
zero crossings in 1s: 880  -&amp;gt; approx freq 440 Hz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;880 crossings per second is 440 Hz, exactly the tone I encoded. The audio in the file is the audio&lt;br&gt;
from the video.&lt;/p&gt;
&lt;h2&gt;
  
  
  Run 2: switch the format and the rate
&lt;/h2&gt;

&lt;p&gt;The format picker is a real dropdown with real choices, so I changed two of them - &lt;code&gt;WebM audio&lt;br&gt;
(Opus)&lt;/code&gt; and &lt;code&gt;48,000 Hz&lt;/code&gt; - and ran it again. The options list for the rate picker was&lt;br&gt;
&lt;code&gt;48,000 Hz&lt;/code&gt;, &lt;code&gt;44,100 Hz&lt;/code&gt;, &lt;code&gt;32,000 Hz&lt;/code&gt;, &lt;code&gt;24,000 Hz&lt;/code&gt;, &lt;code&gt;16,000 Hz&lt;/code&gt;, and the channels list for this&lt;br&gt;
mono source held a single entry, &lt;code&gt;Mono&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The second run produced a different file and a different container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OUTPUT
probe-extract-audio.webm
0:06.0 · 98.1 KiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with &lt;code&gt;Format WebM Opus&lt;/code&gt;, &lt;code&gt;Sample rate 48,000 Hz&lt;/code&gt;, &lt;code&gt;Channels Mono&lt;/code&gt;, &lt;code&gt;Size 98.1 KiB&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ffprobe&lt;/code&gt; on that blob:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;codec_name&lt;/span&gt;=&lt;span class="n"&gt;opus&lt;/span&gt;
&lt;span class="n"&gt;sample_rate&lt;/span&gt;=&lt;span class="m"&gt;48000&lt;/span&gt;
&lt;span class="n"&gt;channels&lt;/span&gt;=&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;format_name&lt;/span&gt;=&lt;span class="n"&gt;matroska&lt;/span&gt;,&lt;span class="n"&gt;webm&lt;/span&gt;
&lt;span class="n"&gt;duration&lt;/span&gt;=&lt;span class="m"&gt;6&lt;/span&gt;.&lt;span class="m"&gt;040000&lt;/span&gt;
&lt;span class="n"&gt;size&lt;/span&gt;=&lt;span class="m"&gt;100485&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the file starts with the four-byte WebM magic &lt;code&gt;1a 45 df a3&lt;/code&gt;. So the picker is not decoration -&lt;br&gt;
it changes the container, the codec and the sample rate of the file you download.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers, side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Rate&lt;/th&gt;
&lt;th&gt;Channels&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;ffprobe says&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;WAV audio (PCM)&lt;/td&gt;
&lt;td&gt;44,100 Hz&lt;/td&gt;
&lt;td&gt;Mono&lt;/td&gt;
&lt;td&gt;519 KiB&lt;/td&gt;
&lt;td&gt;pcm_s16le, 44100 Hz, 1 channel, 6.023 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;WebM audio (Opus)&lt;/td&gt;
&lt;td&gt;48,000 Hz&lt;/td&gt;
&lt;td&gt;Mono&lt;/td&gt;
&lt;td&gt;98.1 KiB&lt;/td&gt;
&lt;td&gt;opus, 48000 Hz, 1 channel, 6.040 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Roughly a fifth of the size for the second run. Neither file is smaller than it needs to be -&lt;br&gt;
uncompressed WAV is uncompressed WAV - but if you are moving audio over a chat window or attaching&lt;br&gt;
it to something, 98 KiB beats 519 KiB, and Opus is the format most players take without asking&lt;br&gt;
questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest edges
&lt;/h2&gt;

&lt;p&gt;A few things this test does &lt;strong&gt;not&lt;/strong&gt; cover, so you know what you are reading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I tested a &lt;strong&gt;mono&lt;/strong&gt; source. The channels picker offered only &lt;code&gt;Mono&lt;/code&gt; for it. A stereo source
should offer &lt;code&gt;Stereo&lt;/code&gt;, but I did not test one, so I am not claiming what the list looks like
then.&lt;/li&gt;
&lt;li&gt;I tested &lt;strong&gt;one&lt;/strong&gt; container, MP4. MOV, MKV, WebM and MPEG-TS are accepted by the file hint, but
only the MP4 was proved.&lt;/li&gt;
&lt;li&gt;The extracted WAV is &lt;strong&gt;6.023 s&lt;/strong&gt; against a &lt;strong&gt;6.000 s&lt;/strong&gt; source. The extra couple of hundredths
are decoder padding, not a different edit.&lt;/li&gt;
&lt;li&gt;The result panel is a summary. The bytes on disk are the evidence, and you can check them the
same way I did.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When it is worth it
&lt;/h2&gt;

&lt;p&gt;The case for doing this in the tab is simple: the file is on your machine already, and you want a&lt;br&gt;
smaller file that is still the same audio. A recording you need as a voice note. A clip from a&lt;br&gt;
screencast you want to send to someone. A track you want to transcribe.&lt;/p&gt;

&lt;p&gt;If you want the walkthrough with screenshots and the button-by-button version, the&lt;br&gt;
&lt;a href="https://app.onepassapps.com/guides?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=audio-extract-format&amp;amp;utm_content=guides-link" rel="noopener noreferrer"&gt;OnePassApps guides&lt;/a&gt;&lt;br&gt;
have it.&lt;/p&gt;

&lt;p&gt;I work on OnePassApps - we build the browser studios behind app.onepassapps.com, where the&lt;br&gt;
processing happens in your tab and the file stays with you.&lt;/p&gt;

</description>
      <category>audio</category>
      <category>webdev</category>
      <category>tools</category>
      <category>ffmpeg</category>
    </item>
    <item>
      <title>Resize an Image in the Browser: Three Numbers That Move Together</title>
      <dc:creator>OnePassApps</dc:creator>
      <pubDate>Tue, 03 Nov 2026 00:47:50 +0000</pubDate>
      <link>https://dev.to/onepassapps/resize-an-image-in-the-browser-three-numbers-that-move-together-3dja</link>
      <guid>https://dev.to/onepassapps/resize-an-image-in-the-browser-three-numbers-that-move-together-3dja</guid>
      <description>&lt;h2&gt;
  
  
  Resizing is three numbers, and only one of them is yours to set
&lt;/h2&gt;

&lt;p&gt;Most resize tools give you a slider or a handful of presets. That is fine until you need an exact pixel size for a form, a sprite sheet or a CMS that rejects anything else - at which point you end up guessing and checking the result by hand.&lt;/p&gt;

&lt;p&gt;The Image Studio's &lt;strong&gt;Resize&lt;/strong&gt; tab in OnePassApps takes the opposite approach: it shows you the width, the height and the scale of your image as live numbers, tells you which of them are locked, and hands you the resized file so you can check it yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full disclosure: I work on OnePassApps.&lt;/strong&gt; This is the Resize tab in the Image Studio, checked in the live tab while writing this - every number below came out of a run I made, not out of the marketing copy.&lt;/p&gt;

&lt;p&gt;Open it here: &lt;a href="https://app.onepassapps.com/studios/image?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=image-resize-dimensions&amp;amp;utm_content=image-studio-link" rel="noopener noreferrer"&gt;the Image Studio&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The panel
&lt;/h2&gt;

&lt;p&gt;The Image Studio carries seven tool tabs on one page: &lt;strong&gt;Convert, Compress, Resize, Crop, Remove background, Remove metadata, Images to PDF&lt;/strong&gt;. The page line above them reads "Convert, compress, resize, crop and clean images in your browser. Nothing leaves this tab."&lt;/p&gt;

&lt;p&gt;The Resize tab is laid out as three numbered steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;01 Source&lt;/strong&gt; - "Add an image to start. Your original stays untouched." The picker accepts JPEG, PNG or WebP · up to 25 MiB and 25 megapixels, and the page repeats "Processing stays in this browser."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;02 Dimensions&lt;/strong&gt; - "Width, height and scale appear here once an image is open. They start at its real size." Three number fields - &lt;strong&gt;Width (px)&lt;/strong&gt;, &lt;strong&gt;Height (px)&lt;/strong&gt; and &lt;strong&gt;Scale (%)&lt;/strong&gt; - then two checkboxes, &lt;strong&gt;Lock aspect&lt;/strong&gt; and &lt;strong&gt;Prevent upscaling&lt;/strong&gt;, a &lt;strong&gt;Format&lt;/strong&gt; row of PNG, JPEG and WebP, a &lt;strong&gt;Quality&lt;/strong&gt; control, and the line "Resampled in this browser, up to 25 megapixels."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;03 Result&lt;/strong&gt; - "The new copy's size and format appear here."&lt;/p&gt;

&lt;p&gt;The action button is &lt;strong&gt;Resize image&lt;/strong&gt;, and after a run it becomes "Resize again". The panel also carries a "Fit" / "100%" preview zoom and a &lt;strong&gt;Result&lt;/strong&gt; / &lt;strong&gt;Original&lt;/strong&gt; toggle so you can compare the two side by side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three numbers, and which one you actually touch
&lt;/h2&gt;

&lt;p&gt;Open a 400 × 300 image and the fields read 400, 300 and 100. Type 100 into the width and the height does not stay at 300 - it becomes 75, and the scale drops to 25. That is the &lt;strong&gt;Lock aspect&lt;/strong&gt; checkbox doing its job: it is on by default, and it keeps the ratio of your image while you resize it.&lt;/p&gt;

&lt;p&gt;Set the scale instead and the other two follow. Scale 50 on the same 400 × 300 image gave 200 × 150 automatically.&lt;/p&gt;

&lt;p&gt;So with the lock on, you type one number and the panel does the arithmetic. The fields are not three separate settings; they are three views of the same decision.&lt;/p&gt;

&lt;p&gt;The limits are visible in the markup rather than buried: width and height run from 1 to 12000, the scale field from 1 to 400, and the quality control from 10 to 100, starting at 86.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checkbox that stops you making the file worse
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Prevent upscaling&lt;/strong&gt; is also on by default, and it is the one that surprises people. With it on, typing a width larger than the source is refused: on the 400 × 300 image, entering 800 left the field reading 400, with the height back at 300 and the scale back at 100.&lt;/p&gt;

&lt;p&gt;Turn it off and the same 800 is accepted. The width field became 800, the height followed to 600, and the scale showed 200.&lt;/p&gt;

&lt;p&gt;That is a deliberate guard rather than a limit on the format. Enlarging a 400 × 300 image to 800 × 600 invents 240,000 pixels that were never in the file - they are filled in by interpolation, so you get a bigger file with less real detail in it. The checkbox is there so that "make it bigger" is something you ask for on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proving the output instead of trusting the panel
&lt;/h2&gt;

&lt;p&gt;Here is the run, in full, on a 400 × 300 PNG I generated myself (four flat colour quadrants with a black marker square in the top-left corner).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run 1 - downscale to 100 px wide.&lt;/strong&gt; Width 100, lock aspect on, default JPEG. The panel reported:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Before 1.8KiB PNG"&lt;/li&gt;
&lt;li&gt;"After 1.3KiB JPEG"&lt;/li&gt;
&lt;li&gt;"Dimensions 400 × 300 → 100 × 75"&lt;/li&gt;
&lt;li&gt;"File name probe-resize.jpg"&lt;/li&gt;
&lt;li&gt;"Resized in 0.2 s"&lt;/li&gt;
&lt;li&gt;"JPEG · 100 × 75 · 1.3 KiB · 24% smaller"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exported file was a real JPEG - 1,378 bytes with a JFIF header. Reading its pixels back, the top-left pixel was black (0,0,0,255), the top-right green (0,255,0,255), the bottom-left blue (0,0,255,255) and the bottom-right white (255,255,255,255): the same four quadrants, resampled into the smaller frame, in the same places.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run 2 - upscale to 800 px wide.&lt;/strong&gt; Prevent upscaling off, width 800. The panel reported "Dimensions 400 × 300 → 800 × 600", "After 6.9KiB JPEG" and "JPEG · 800 × 600 · 6.9 KiB · 290% larger". The exported file was 7,064 bytes of JPEG, and the four corner pixels still read black, green, blue and white.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run 3 - PNG output at 120 px wide.&lt;/strong&gt; Format PNG, width 120, height followed to 90, scale 30. The panel reported "After 618B PNG", "Dimensions 400 × 300 → 120 × 90", "PNG · 120 × 90 · 618 B · 66% smaller". The exported file's IHDR header read 120 × 90, and its corner pixels read black, green, blue and white again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The default output format is JPEG, and the size line can read "larger"
&lt;/h2&gt;

&lt;p&gt;The Format row defaults to JPEG, so the first resize I made came out as "probe-resize.jpg" even though the source was a PNG.&lt;/p&gt;

&lt;p&gt;Read the size line before you take it. Upscaling that flat-colour PNG to 800 × 600 produced "6.9 KiB · 290% larger" - the file got three and a half times the size of the original, because JPEG re-encodes what PNG had stored losslessly and the result is still only 400 × 300 of real pixels. The panel is not hiding it: it shows the before and after sizes, the percentage, and a Result / Original toggle so you can compare the two.&lt;/p&gt;

&lt;p&gt;Pick &lt;strong&gt;PNG&lt;/strong&gt; when you want a lossless result. The same downscale to 120 × 90 came out at 618 bytes, against 1.8 KiB for the source - 66% smaller, and every pixel in it still exactly matches the colour it came from.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get at the end
&lt;/h2&gt;

&lt;p&gt;The result panel gives you the file name, the format, the pixel dimensions, the size change, the time it took ("Resized in 0.2 s"), and a &lt;strong&gt;Download&lt;/strong&gt; button. The closing line is the honest one: "Finished. Download a new copy; the original stays unchanged." Your source file is never overwritten, and you keep both copies side by side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do it in the tab at all
&lt;/h2&gt;

&lt;p&gt;The usual reason to resize something is that it goes somewhere you do not control - an upload box that wants exactly 800 × 600, a form with a size limit, an avatar field that wants a square. Those are exactly the files you would rather not hand to a stranger's server, and this tab never sends them: the page says "Nothing leaves this tab." and "Processing stays in this browser.", and the only thing that leaves the browser is the file you download yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full disclosure again: I work on OnePassApps.&lt;/strong&gt; The Image Studio is free right now during the promotional period - Standard is $0/month with 100,000 credits every 30 days, no end date set and no card required, and existing paid subscriptions keep their terms. If you want the other tabs in the same place, the guides are here: &lt;a href="https://app.onepassapps.com/guides?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=image-resize-dimensions&amp;amp;utm_content=guides-link" rel="noopener noreferrer"&gt;OnePassApps app guides&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>image</category>
      <category>tools</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How NewsFlow Builds a Brief: Source Attribution, Key Points and the Queue</title>
      <dc:creator>OnePassApps</dc:creator>
      <pubDate>Tue, 03 Nov 2026 00:29:55 +0000</pubDate>
      <link>https://dev.to/onepassapps/how-newsflow-builds-a-brief-source-attribution-key-points-and-the-queue-2ohd</link>
      <guid>https://dev.to/onepassapps/how-newsflow-builds-a-brief-source-attribution-key-points-and-the-queue-2ohd</guid>
      <description>&lt;p&gt;I work on OnePassApps, and NewsFlow is the hosted half of this product. The seven Studios run inside your browser tab; NewsFlow briefs are prepared on our servers and attributed back to the publication they came from. Here is what the app actually does when you open it - including the parts it deliberately does not do.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four views
&lt;/h2&gt;

&lt;p&gt;Opening the app gives you four tabs: Inbox, Queue, Saved and Sources, with a "Sign in to sync" button in the header.&lt;/p&gt;

&lt;p&gt;The Inbox is a list of recent stories. Every row follows the same shape - source name, byline, relative time, headline, then the brief itself. One row read "Universe Today · Evan Gough · 4h" above the headline "The JWST May Have Found Where Heavy Black Hole Seeds Formed". Above the list sit source filters (All, Universe Today, Electronic Frontier Foundation, Global Voices) and a count line reading "5 stories available."&lt;/p&gt;

&lt;p&gt;Small honest note: I opened the app twice while writing this and the counter read 5 the first time and 4 the second, with the tab badge moving from "5 new" to "4 new". The window moves while you look at it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of a brief
&lt;/h2&gt;

&lt;p&gt;Selecting a story opens a detail panel with three labelled blocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BRIEF&lt;/strong&gt; - the summary paragraphs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;KEY POINTS&lt;/strong&gt; - numbered 01, 02, 03&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SOURCES&lt;/strong&gt; - "Read the original on universetoday.com (opens in a new tab)", with a "Sources &amp;amp; credits" control&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The header of that panel reads "2 November 2026 · Brief from 1 source". The key points for the JWST story were "JWST may have found the ancient sources of IMBH heavy seeds." and "Young massive clusters in the high-redshift Universe are proposed as those sources."&lt;/p&gt;

&lt;p&gt;The design decision underneath the whole app is that a summary is never unattributed. If a sentence is in the brief, there is a source line behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The queue and the audio
&lt;/h2&gt;

&lt;p&gt;The Queue tab is titled "Up next". On a fresh, signed-out visit it says "Your queue is empty" and "Add stories from Inbox or a brief to hear them in order.", with a "Go to Inbox" button.&lt;/p&gt;

&lt;p&gt;The story panel carries the audio controls: a "Listen to this brief" button, a playback-rate control reading 1×, and an "Autoplay" toggle. Under the plan block the guest limit is stated in plain words: "Guests get 10 free listens a day. Sign in for more." I did not play audio during this test - I read the controls and the copy above them, so treat the listening behaviour as what the app says about itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sources you can and cannot choose
&lt;/h2&gt;

&lt;p&gt;The Sources tab lists the catalogue with a category filter (All sources, News, Technology, Entertainment, Social, Research, Podcasts &amp;amp; video) and a live count per entry, such as "arXiv cs.AI" with "0 stories today · Research" and "Electronic Frontier Foundation" with "1 story today · Technology". The header reads "9 followed", and the note underneath is the one that matters: "Starter sources are read-only. Sign in to choose your own."&lt;/p&gt;

&lt;p&gt;Signed out, you read and listen to a fixed starter set. Signed in, you choose your own sources.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;Briefs and listening are prepared at no credit cost. The app guide puts it directly: "Briefs and listening are prepared at no credit cost." Guests read and listen to the public Inbox within shared capacity and usage limits, and a free account is what changes the sources you follow.&lt;/p&gt;

&lt;p&gt;The plan block on the page reads "FREE PROMOTIONAL PERIOD", "Standard is free right now", "$0 / month" and "100,000 credits every 30 days · No end date set · No card required", followed by "No automatic paid subscription. Existing paid subscriptions keep their terms."&lt;/p&gt;

&lt;h2&gt;
  
  
  The limits, stated plainly
&lt;/h2&gt;

&lt;p&gt;Three honest edges, taken from the app's own guide:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;"The current Inbox covers a recent time window, not a complete archive." NewsFlow is not an archive and does not pretend to be one.&lt;/li&gt;
&lt;li&gt;"Original-only sources do not offer generated briefs or audio." Not every source gets a brief.&lt;/li&gt;
&lt;li&gt;Guest source selection is read-only. Fresh summary generation and account source changes need a sign-in.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One more worth saying out loud: NewsFlow is a hosted app. The browser-local guarantee in this product covers the seven Studios - Image, Audio, File, Text, Data, Icon and Colour - not NewsFlow, which prepares briefs on our servers.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://app.onepassapps.com/apps/newsflow?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=newsflow-briefs&amp;amp;utm_content=newsflow-link" rel="noopener noreferrer"&gt;Open NewsFlow&lt;/a&gt; in your browser, or read the full &lt;a href="https://app.onepassapps.com/guides/newsflow?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=newsflow-briefs&amp;amp;utm_content=guides-link" rel="noopener noreferrer"&gt;app guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I work on OnePassApps. If you try it and something reads wrong, mail &lt;a href="mailto:info@onepassapps.com"&gt;info@onepassapps.com&lt;/a&gt; and tell me which part.&lt;/p&gt;

</description>
      <category>newsflow</category>
      <category>news</category>
      <category>webdev</category>
      <category>tools</category>
    </item>
    <item>
      <title>Change Text Case in the Browser: What the Eight Styles Do to Apostrophes and Digits</title>
      <dc:creator>OnePassApps</dc:creator>
      <pubDate>Tue, 03 Nov 2026 00:08:36 +0000</pubDate>
      <link>https://dev.to/onepassapps/change-text-case-in-the-browser-what-the-eight-styles-do-to-apostrophes-and-digits-1k6k</link>
      <guid>https://dev.to/onepassapps/change-text-case-in-the-browser-what-the-eight-styles-do-to-apostrophes-and-digits-1k6k</guid>
      <description>&lt;p&gt;I work on OnePassApps. We build browser studios that do their work in the tab instead of uploading your files, and Text Studio is the one for plain text: eight tools on one tab bar - Clean, Case, Duplicate lines, Sort lines, Find &amp;amp; replace, Diff, Markdown and Base64.&lt;/p&gt;

&lt;p&gt;The tab introduces itself in one line: "Count, clean, compare and convert text locally. Your content stays in this browser tab." It looks like the least interesting of the eight until you feed it something that is not a plain sentence. There is one dropdown with eight styles in it, and the whole job is a string transform - but the punctuation handling is where it gets interesting. The same input produces eight different outputs, and none of them is what a naive regex would give you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the tab shows
&lt;/h2&gt;

&lt;p&gt;Three numbered panels, the same shape as the other Text Studio tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;01 Input text&lt;/strong&gt; - a 2,000,000 character counter and a live stats row underneath: Words, Characters, Sentences, Lines and UTF-8 bytes. The placeholder reads "Paste or type text here. Nothing is uploaded."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;02 Options&lt;/strong&gt; - one control, "Case style". It is a combobox whose button label is the style you currently have, so the trigger reads "UPPERCASE" until you change it. The listbox it opens holds eight options: UPPERCASE, lowercase, Title Case, Sentence case, camelCase, PascalCase, snake_case and kebab-case. The keyboard works as well - from the trigger, two ArrowDown presses and Enter moved the selection from UPPERCASE to lowercase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;03 Result&lt;/strong&gt; - "Converted text shows on the stage, ready to copy, download or use as the new input."&lt;/p&gt;

&lt;p&gt;The action button is labelled "Convert case" and it becomes "Convert again" after a run. The finished text sits under the heading "Text result" with the note "Text transformed locally." Under the stage the page says "Nothing leaves this tab." and further down "Processing stays in this browser." You can drop a file in instead of pasting - the drop hint reads ".txt, .md, .csv and other text up to 8 MiB - or paste it in Input text - nothing is uploaded".&lt;/p&gt;

&lt;h2&gt;
  
  
  Eight styles, one sample
&lt;/h2&gt;

&lt;p&gt;I put this 37-character line in the box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;onepass apps: 7 studios, 1 membership
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and ran it through every style in the dropdown. These are the outputs, verbatim, with the character count the result panel reported:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Style&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Characters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UPPERCASE&lt;/td&gt;
&lt;td&gt;ONEPASS APPS: 7 STUDIOS, 1 MEMBERSHIP&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lowercase&lt;/td&gt;
&lt;td&gt;onepass apps: 7 studios, 1 membership&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Title Case&lt;/td&gt;
&lt;td&gt;Onepass Apps: 7 Studios, 1 Membership&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sentence case&lt;/td&gt;
&lt;td&gt;Onepass apps: 7 studios, 1 membership&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;camelCase&lt;/td&gt;
&lt;td&gt;onepassApps7Studios1Membership&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PascalCase&lt;/td&gt;
&lt;td&gt;OnepassApps7Studios1Membership&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;snake_case&lt;/td&gt;
&lt;td&gt;onepass_apps_7_studios_1_membership&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kebab-case&lt;/td&gt;
&lt;td&gt;onepass-apps-7-studios-1-membership&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every run came back "Ready" with "Converted in 0.1 s", and the result line counted the output - "37 characters · 1 line" for the first four styles, "30 characters · 1 line" for the camelCase pair, "35 characters · 1 line" for the two separator styles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things the table hides
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;camelCase and PascalCase delete the punctuation.&lt;/strong&gt; The colon and the comma do not become separators - they are removed. That is where the seven missing characters go: 37 in, 30 out. A digit is not a word boundary either, so "7 studios" becomes "7Studios" glued to the word before it. If you are generating a JavaScript identifier from a human sentence, this is the style that will quietly merge two of your fields.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;snake_case and kebab-case keep punctuation as a boundary.&lt;/strong&gt; The same colon and comma become word breaks, so the output is longer than the camelCase pair (35 against 30) and every token is cleanly separable. The digit keeps its own token in these two styles: "&lt;em&gt;7_studios&lt;/em&gt;", not "&lt;em&gt;7studios&lt;/em&gt;".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Title Case does not know about your brand names.&lt;/strong&gt; "onepass" came back as "Onepass", not "OnePass". Title Case capitalises the first letter of each word and leaves the rest of the word exactly as it found it, so any internal capital you typed survives only in the styles that do not touch it. Sentence case behaved the same way on the first word - "Onepass apps: 7 studios, 1 membership" - while lowercasing the rest of the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  The apostrophe is the sharp edge
&lt;/h2&gt;

&lt;p&gt;The second sample was a 30-character string with an apostrophe, a hyphen and a bracketed year:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;the user's guide-part 2 (2026)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Title Case left it as "The User's Guide-Part 2 (2026)" - the hyphen capitalises the word after it, the digits and the brackets are untouched. Sentence case gave "The user's guide-part 2 (2026)".&lt;/p&gt;

&lt;p&gt;The identifier styles disagreed with each other. camelCase produced "theUserSGuidePart22026" and PascalCase produced "TheUserSGuidePart22026" - in both, the apostrophe split "user's" into two tokens, so the "s" ended up standing on its own as "S" with a capital of its own. snake_case turned the same input into "the_user_s_guide_part_2_2026" and kebab-case into "the-user-s-guide-part-2-2026", where the same split shows up as a separate "&lt;em&gt;s&lt;/em&gt;" segment.&lt;/p&gt;

&lt;p&gt;So the possessive in "user's" becomes a real segment in the output. If the string came from a name, a title or a URL slug, that is the difference between "user_s" and "user" in your database.&lt;/p&gt;

&lt;p&gt;Both of those identifier styles also dropped the length: 30 characters in, 22 out for camelCase and PascalCase, 28 out for snake_case and kebab-case.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bit that saves you re-pasting
&lt;/h2&gt;

&lt;p&gt;The result stage gives you three buttons: Copy, Download .txt and Use as input. "Use as input" pushes the converted text back into the input box so you can convert again without re-typing. I used it after the kebab-case run and the input field then read "the-user-s-guide-part-2-2026" - the input box took the previous output, ready for the next transform. There is also a "Clear all" button that empties the input.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;The plan block on the page reads: FREE PROMOTIONAL PERIOD, Standard is free right now, $0 / month, 100,000 credits every 30 days, No end date set, No card required. It also says "No automatic paid subscription. Existing paid subscriptions keep their terms."&lt;/p&gt;

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

&lt;p&gt;The Case tab is here: &lt;a href="https://app.onepassapps.com/studios/text?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=text-case-punctuation&amp;amp;utm_content=text-studio-link" rel="noopener noreferrer"&gt;Text Studio&lt;/a&gt;. Every conversion above happened in the tab, on your machine, and nothing was uploaded to do it.&lt;/p&gt;

&lt;p&gt;If you want the shorter version of the same job for a different job - a table, a file, a colour, an icon - the other studios are listed here: &lt;a href="https://app.onepassapps.com/guides?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=text-case-punctuation&amp;amp;utm_content=guides-link" rel="noopener noreferrer"&gt;OnePassApps guides&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>text</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>tools</category>
    </item>
    <item>
      <title>Clean a Text File in the Browser: Line Endings, Trailing Spaces and Blank Lines</title>
      <dc:creator>OnePassApps</dc:creator>
      <pubDate>Mon, 02 Nov 2026 23:51:51 +0000</pubDate>
      <link>https://dev.to/onepassapps/clean-a-text-file-in-the-browser-line-endings-trailing-spaces-and-blank-lines-5bdf</link>
      <guid>https://dev.to/onepassapps/clean-a-text-file-in-the-browser-line-endings-trailing-spaces-and-blank-lines-5bdf</guid>
      <description>&lt;p&gt;I work on OnePassApps. We build browser studios that do their work in the tab instead of uploading your files, and Text Studio is the one for plain text: eight tools on one tab bar - Clean, Case, Duplicate lines, Sort lines, Find &amp;amp; replace, Diff, Markdown and Base64.&lt;/p&gt;

&lt;p&gt;The Clean tab is the least glamorous of the eight, and the one you reach for most often: it takes a messy text file and gives you back a tidy one, without a round trip to a server.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the tab shows
&lt;/h2&gt;

&lt;p&gt;Three numbered panels, the same shape as the other Text Studio tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;01 Input text&lt;/strong&gt; - a 2,000,000 character counter, and a live stats row underneath: Words, Characters, Sentences, Lines and UTF-8 bytes. The placeholder reads "Paste or type text here. Nothing is uploaded."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;02 Cleaning options&lt;/strong&gt; - five checkboxes, and the panel tells you how many are on ("3 of 5 on" by default):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Normalize line endings to LF&lt;/li&gt;
&lt;li&gt;Trim line-end spaces&lt;/li&gt;
&lt;li&gt;Trim outer whitespace&lt;/li&gt;
&lt;li&gt;Collapse repeated blank lines&lt;/li&gt;
&lt;li&gt;Remove blank lines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;03 Result&lt;/strong&gt; - "Cleaned text shows on the stage, ready to copy, download or use as the new input."&lt;/p&gt;

&lt;p&gt;The action button is labelled "Clean text", and it becomes "Clean again" after a run. Under the stage the finished text sits under the heading "Text result", with the note "Text transformed locally."&lt;/p&gt;

&lt;h2&gt;
  
  
  What I ran
&lt;/h2&gt;

&lt;p&gt;I typed a deliberately untidy 40-character input into the field: leading spaces and blank lines at the top, a line with trailing spaces, a run of blank lines in the middle, a tab character before the last line, and trailing spaces at the end.&lt;/p&gt;

&lt;p&gt;The counter read "40 / 2,000,000" and the stats row showed Words 4, Characters 40, Sentences 0, Lines 8, UTF-8 bytes 40.&lt;/p&gt;

&lt;p&gt;With Normalize line endings to LF, Trim line-end spaces, Trim outer whitespace and Remove blank lines enabled (the panel showed "4 of 5 on"), I pressed "Clean text". The stage came back "Ready" with "Cleaned in 0.1 s", and the result line read "Cleaned text 23 characters - 2 lines". The output was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alpha line
   beta line
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things are worth reading out of that output. The three spaces in front of "beta line" survived: this tool tidies the outer edges of the file and the end of each line, it does not re-indent your content. And the count fell from 40 characters to 23 because the blank lines and the trailing whitespace went - not because the text was rewritten.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copy it, or make it the new input
&lt;/h2&gt;

&lt;p&gt;The result panel offers Copy, Download .txt and Use as input. "Use as input" puts the cleaned text straight back into the field on the left, so you can clean again - run Find &amp;amp; replace next, or diff the result against another version of the same file.&lt;/p&gt;

&lt;h2&gt;
  
  
  The limits, as the page states them
&lt;/h2&gt;

&lt;p&gt;You can drop a file or paste: ".txt, .md, .csv and other text up to 8 MiB - or paste it in Input text - nothing is uploaded". "Nothing leaves this tab." and "Processing stays in this browser." The plan block on the page reads FREE PROMOTIONAL PERIOD, Standard is free right now, $0 / month, 100,000 credits every 30 days, no end date set and no card required, with no automatic paid subscription.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use it
&lt;/h2&gt;

&lt;p&gt;Open &lt;a href="https://app.onepassapps.com/studios/text?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=text-clean-whitespace&amp;amp;utm_content=text-studio-link" rel="noopener noreferrer"&gt;Text Studio&lt;/a&gt;, pick the Clean tab, paste the file you were about to strip by hand, and read the result before you copy it. The &lt;a href="https://app.onepassapps.com/guides?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=text-clean-whitespace&amp;amp;utm_content=guides-link" rel="noopener noreferrer"&gt;app guides&lt;/a&gt; cover the rest of the Studio.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>text</category>
      <category>tools</category>
    </item>
  </channel>
</rss>
