<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>webdev &amp;mdash; Roundup Issue Tracker</title>
    <link>https://blog.rouilj.dynamic-dns.net/rouilj/tag:webdev</link>
    <description>A blog about developing and using the Roundup Issue Tracker. Covers web design/front end development, Python, security, user interface, customization, documentation, and community development.</description>
    <pubDate>Tue, 05 May 2026 10:24:18 -0400</pubDate>
    <item>
      <title>Copy issue reference to clipboard enhancement</title>
      <link>https://blog.rouilj.dynamic-dns.net/rouilj/copy-issue-reference-to-clipboard-enhancement</link>
      <description>&lt;![CDATA[On the Roundup Issue Tracker mailing list a user asked for a mechanism to create a reference for the current issue and copy it to the clipboard.&#xA;&#xA;The request was for the reference to look like:&#xA;  issue2345: title of issue&#xA;In Roundup, object designators like issue2345 are automatically hyperlinked to the corresponding object.&#xA;&#xA;The code I recommended used a button to trigger this operation. It also used the clipboard API. When clicking on the button (or hitting space or return while focused on the button) the reference format above will be copied to the clipboard. Triggering the clipboard API must be done by a user interaction. For example: activating a button. If the browser does not support the clipboard API, a simple alert() is shown.&#xA;&#xA;In addition to copying to the clipboard, the user gets feedback when the text of the button changes to &#34;Reference copied&#34;. Then it resets to the original message after 2 seconds. Since clicking the button multiple times is idempotent, there is no sense in disabling or debouncing the button.&#xA;!--more--&#xA;Note, this is unlikely to be a11y compliant as I don&#39;t think the change of button text is announced. If anybody has some ideas on how to make this more compliant, leave them in the comments. Maybe using aria-live=&#34;polite&#34; or &#34;assertive&#34; on the button element would do the trick?&#xA;&#xA;Here is the code I suggested:&#xA;&lt;button id=&#34;copyreference&#34; type=&#34;button&#34;&#xA;            tal:condition=&#34;context/iseditok&#34;  Copy Reference&#xA;/button&#xA;&#xA;script tal:attributes=&#34;nonce request/client/clientnonce&#34;&#xA;  (function () {&#xA;    &#34;use strict&#34;;&#xA;&#xA;    let crb = document.querySelector(&#34;#copyreference&#34;);&#xA;    if ( ! crb ) return&#xA;    crb.addEventListener(&#34;click&#34;, async (e) =  {&#xA;        e.preventDefault()&#xA;        if ( ! navigator.clipboard ) {&#xA;            alert(&#34;Clipboard is not available&#34;)&#xA;            return&#xA;        }&#xA;&#xA;        let issueDesignator = new URL(document.URL)&#xA;            .pathname&#xA;            .split(&#34;/&#34;)&#xA;            .pop()&#xA;        let issueTitleText = document.querySelector(&#34;#title&#34;).value;&#xA;&#xA;        await navigator.clipboard.writeText(${issueDesignator}: ${issueTitleText})&#xA;&#xA;        let originalCrbInnerText = crb.innerText&#xA;        crb.innerText = &#34;Reference copied&#34;&#xA;        setTimeout(() =  {&#xA;            crb.innerText = originalCrbInnerText}, 2000)&#xA;        }&#xA;    );&#xA;  })();&#xA;&#xA;The flexibility of Roundup allows the administrator to rewrite all of the HTML used in the web interface. As a result, I based my guess of CSS selectors on the HTML generated by the classic issue.item.html TAL template.&#xA;&#xA;The button could be placed anywhere on the page and the script (including the nonce required by the CSP) would be placed anywhere after the button. Probably at the end of the page so it doesn&#39;t block rendering.&#xA;&#xA;The classic structure of the issue display page for users with editing capability included an input with the id title. This is retrieved using the id and included in the string written to the clipboard.&#xA;&#xA;A user without editing capability for the issue (or without editing capability for the title attribute of an issue) does not have an input with id=&#34;title&#34;.&#xA;In these cases, the admin would have to modify the issue.item.html template to add an id of title to the enclosing element. Then the code above would be modified to replace:&#xA;&#xA;let issueTitleText = document.querySelector(&#34;#title&#34;).value;&#xA;with:&#xA; let issueTitle = document.querySelector(&#34;input#title&#34;);&#xA;   if ( ! issueTitle ) {&#xA;     issueTitle = document.querySelector(&#34;#title&#34;);&#xA;     issueTitleText = issueTitle.innerText&#xA;   } else {&#xA;     issueTitleText = issueTitle.value&#xA;   }&#xA;&#xA;In my original example, the button element is generated only if the user can edit the issue. Removing the tal:condition attribute would always display the button.&#xA;&#xA;There is nothing with an id or CSS selector that contains the object designator. I use the final element of the path of the URL to get the designator.&#xA;&#xA;I chose to use the IIFE code structure. This allows me to use the early return pattern if the button is not found. If anybody knows how to do the equivalent of an early return without an IIFE or other function in a script tag, leave your trick in the comments.&#xA;&#xA;If you use the Roundup Issue Tracker and have your own enhancements, feel free to mention them in the comments.&#xA;&lt;!--&#xA;{% embed https://www.roundup-tracker.org/?ref=devto %}&#xA;--  #WebDev, #opensource #RoundupTracker  #DevTo&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>On the <a href="https://sourceforge.net/p/roundup/mailman/search/?q=Feature+Request+%3A+Copy+Issue+id+and+title+separated+by+%27%3B+%27+to+the+clipboard&amp;mail_list=roundup-users" rel="nofollow">Roundup Issue Tracker mailing list</a> a user asked for a mechanism to create a reference for the current issue and copy it to the clipboard.</p>

<p>The request was for the reference to look like:</p>

<pre><code>  issue2345: title of issue
</code></pre>

<p>In Roundup, object designators like <code>issue2345</code> are automatically hyperlinked to the corresponding object.</p>

<p>The code I recommended used a button to trigger this operation. It also used the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API" rel="nofollow">clipboard API</a>. When clicking on the button (or hitting <code>space</code> or <code>return</code> while focused on the button) the reference format above will be copied to the clipboard. Triggering the clipboard API must be done by a user interaction. For example: activating a button. If the browser does not support the clipboard API, a simple <code>alert()</code> is shown.</p>

<p>In addition to copying to the clipboard, the user gets feedback when the text of the button changes to “Reference copied”. Then it resets to the original message after 2 seconds. Since clicking the button multiple times is idempotent, there is no sense in disabling or debouncing the button.

Note, this is unlikely to be a11y compliant as I don&#39;t think the change of button text is announced. If anybody has some ideas on how to make this more compliant, leave them in the comments. Maybe using <code>aria-live=&#34;polite&#34;</code> or <code>&#34;assertive&#34;</code> on the <code>button</code> element would do the trick?</p>

<p>Here is the code I suggested:</p>

<pre><code class="language-html">&lt;button id=&#34;copyreference&#34; type=&#34;button&#34;
            tal:condition=&#34;context/is_edit_ok&#34;&gt;
   Copy Reference
&lt;/button&gt;

&lt;script tal:attributes=&#34;nonce request/client/client_nonce&#34;&gt;
  (function () {
    &#34;use strict&#34;;

    let crb = document.querySelector(&#34;#copyreference&#34;);
    if ( ! crb ) return
    crb.addEventListener(&#34;click&#34;, async (e) =&gt; {
        e.preventDefault()
        if ( ! navigator.clipboard ) {
            alert(&#34;Clipboard is not available&#34;)
            return
        }

        let issueDesignator = new URL(document.URL)
            .pathname
            .split(&#34;/&#34;)
            .pop()
        let issueTitleText = document.querySelector(&#34;#title&#34;).value;

        await navigator.clipboard.writeText(`${issueDesignator}: ${issueTitleText}`)

        let originalCrbInnerText = crb.innerText
        crb.innerText = &#34;Reference copied&#34;
        setTimeout(() =&gt; {
            crb.innerText = originalCrbInnerText}, 2000)
        }
    );
  })();
</code></pre>

<p>The <a href="https://www.roundup-tracker.org/docs/features.html" rel="nofollow">flexibility of Roundup</a> allows the administrator to rewrite all of the HTML used in the web interface. As a result, I based my guess of CSS selectors on the <a href="https://sourceforge.net/p/roundup/code/ci/default/tree/share/roundup/templates/classic/html/issue.item.html" rel="nofollow">HTML generated by the classic <code>issue.item.html</code> TAL template</a>.</p>

<p>The button could be placed anywhere on the page and the script (including the nonce required by the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP" rel="nofollow">CSP</a>) would be placed anywhere after the button. Probably at the end of the page so it doesn&#39;t block rendering.</p>

<p>The classic structure of the issue display page for users with editing capability included an input with the id <code>title</code>. This is retrieved using the id and included in the string written to the clipboard.</p>

<p>A user without editing capability for the issue (or without editing capability for the title attribute of an issue) does not have an input with <code>id=&#34;title&#34;</code>.
In these cases, the admin would have to modify the <code>issue.item.html</code> template to add an id of title to the enclosing element. Then the code above would be modified to replace:</p>

<pre><code class="language-js">let issueTitleText = document.querySelector(&#34;#title&#34;).value;
</code></pre>

<p>with:</p>

<pre><code class="language-js"> let issueTitle = document.querySelector(&#34;input#title&#34;);
   if ( ! issueTitle ) {
     issueTitle = document.querySelector(&#34;#title&#34;);
     issueTitleText = issueTitle.innerText
   } else {
     issueTitleText = issueTitle.value
   }
</code></pre>

<p>In my original example, the button element is generated only if the user can edit the issue. Removing the <code>tal:condition</code> attribute would always display the button.</p>

<p>There is nothing with an id or CSS selector that contains the object designator. I use the final element of the path of the URL to get the designator.</p>

<p>I chose to use the <a href="https://en.wikipedia.org/wiki/Immediately-invoked_function_expression" rel="nofollow">IIFE</a> code structure. This allows me to <a href="https://gomakethings.com/the-early-return-pattern-in-javascript/" rel="nofollow">use the early return pattern</a> if the button is not found. If anybody knows how to do the equivalent of an early return without an IIFE or other function in a script tag, leave your trick in the comments.</p>

<p>If you use the Roundup Issue Tracker and have your own enhancements, feel free to mention them in the comments.

<a href="/rouilj/tag:WebDev" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">WebDev</span></a>, <a href="/rouilj/tag:opensource" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">opensource</span></a> <a href="/rouilj/tag:RoundupTracker" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">RoundupTracker</span></a>  <a href="/rouilj/tag:DevTo" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">DevTo</span></a></p>
]]></content:encoded>
      <guid>https://blog.rouilj.dynamic-dns.net/rouilj/copy-issue-reference-to-clipboard-enhancement</guid>
      <pubDate>Thu, 09 Nov 2023 00:01:02 +0000</pubDate>
    </item>
    <item>
      <title>To Release or Not to Release: What&#39;s a Good Schedule</title>
      <link>https://blog.rouilj.dynamic-dns.net/rouilj/to-release-or-not-to-release-whats-a-good-schedule</link>
      <description>&lt;![CDATA[&#xA;&#xA;Shakespeare once said &#34;A rose by any other name would smell as sweet&#34;. I wonder if something similar can be said for software releases.&#xA;&#xA;I am planning a new release of the opensource Roundup Issue Tracker. Historically, release schedules were on an as needed basis. Before 2006 (when version 1.0.0 was released) there were multiple years with more than 10 releases (including alpha/beta and other pre releases).&#xA;&#xA;| Year |  Releases| Year |  Releases|&#xA;|:----:| --------:|:----:| --------:|&#xA;| 2022 |        2 | 2009 |        5 |&#xA;| 2021 |        2 | 2008 |        5 |&#xA;| 2020 |        2 | 2007 |        3 |&#xA;| 2019 |        2 | 2006 |       13 |&#xA;| 2018 |        1 | 2005 |        8 |&#xA;| 2016 |        1 | 2004 |       19 |&#xA;| 2013 |        1 | 2003 |       16 |&#xA;| 2012 |        2 | 2002 |       12 |&#xA;| 2011 |        3 | 2001 |        7 |&#xA;| 2010 |        5 |&#xA;&#xA;But there were no releases from 2013 until 2016. Also, there was a gap in 2017. There was significant development done during those years without releases. But only those willing to run code from the repository were able to benefit.  In 2018 I took over as release manager and chose to start doing yearly releases.&#xA;&#xA;Why Release&#xA;&#xA;With opensource projects, there are many reasons to release:&#xA;!--more--&#xA;get features/bug fixes to users&#xA;thank contributors&#xA;increase project visibility&#xA;establish expectations&#xA;provide deadlines&#xA;&#xA;As I mentioned above, producing a release makes the work of contributors (developers, documentation writers, people reporting issues) isn&#39;t generally available. Frequent releases align with my preference to reduce WIP (work in progress). Thanking the contributors by releasing their work helps drive enthusiasm and engagement.&#xA;&#xA;Announcements on multiple platforms accompany each release. When I see another issue tracking/bug reporting program release announcement, I experience FOMO over the marketing provided by their announcement.&#xA;&#xA;A regular release cadence helps users to schedule time for testing or upgrading the software. Also, regularly scheduled releases encourage  developers to finish their work and get it into a release. This deadline also drives me to go through all the steps to generate a beta and final release. This year (2023) will be my 6th yearly release.&#xA;&#xA;When to Release&#xA;&#xA;Although I do believe that reducing WIP is important, Roundup is not provided as a SAS (software as a service). It is deployed on-prem and meant to be customized. Some changes to Roundup require changes to a customized installation as part of the upgrade. For example:&#xA;&#xA;upgrade from Python 2 to Python 3 can require rewriting a tracker&#39;s business logic&#xA;support for newer databases versions can require table alteration (sometimes manual, sometimes programmatic using the roundup-admin migrate command)&#xA;security improvements and CSRF protection require changes to customized HTML templates.&#xA;&#xA;I mentioned my FOMO when seeing other products release multiple times a year. I often see releases with 5 or so changes.  Often these changes fix cosmetic issues or broken fields in the web interface.&#xA;&#xA;With Roundup, many users have redesigned/replaced part of the interface. A release only supplies changes to the reference interfaces. The Roundup administrator needs to merge the reference changes into their production interface. In Roundup, changes can be implemented in production without requiring a release. This separation of concerns between the Roundup core and a customizable tracker makes Roundup more flexible. But, increases the work required during an upgrade.&#xA;&#xA;Because of how customizable Roundup is, I try to balance between WIP and the churn caused by frequent releases. Yearly seems to be a sweet spot. There are occasions when a new core feature needs a change/bug fix. In that case the user has a choice to wait for a new release or use the most recent commit to the development repository.&#xA;&#xA;If Roundup was a SAS with more limited customizability, we could do canary deploys directly from the repository. This would positively affect:&#xA;&#xA;getting features/bug fixes to users&#xA;thank contributors&#xA;&#xA;but would also reduce the utility of Roundup. Setting up a Getting Things Done tracker for his/her family would find it very difficult in a SAS based model. Also, the tempo of releases in a SAS based model may not align with the user&#39;s needs for a stable platform.&#xA;&#xA;If Shakespeare were a software writer would he have penned &#34;Would software by another release schedule smell as sweet?&#34;. Leave your thoughts in the comments.&#xA;&#xA;(Cover Image by Glen Carrie)&#xA;&#xA;#webdev, #discuss, #opensource, #development&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mwyvykplf28yj7kweg70.jpg" alt="" title="Banner Image"></p>

<p>Shakespeare once said “A rose by any other name would smell as sweet”. I wonder if something similar can be said for software releases.</p>

<p>I am planning a new release of the opensource <a href="https://www.roundup-tracker.org" rel="nofollow">Roundup Issue Tracker</a>. Historically, release schedules were on an as needed basis. Before 2006 (when version 1.0.0 was released) there were multiple years with more than 10 releases (including alpha/beta and other pre releases).</p>

<table>
<thead>
<tr>
<th align="center">Year</th>
<th align="right">Releases</th>
<th align="center">Year</th>
<th align="right">Releases</th>
</tr>
</thead>

<tbody>
<tr>
<td align="center">2022</td>
<td align="right">2</td>
<td align="center">2009</td>
<td align="right">5</td>
</tr>

<tr>
<td align="center">2021</td>
<td align="right">2</td>
<td align="center">2008</td>
<td align="right">5</td>
</tr>

<tr>
<td align="center">2020</td>
<td align="right">2</td>
<td align="center">2007</td>
<td align="right">3</td>
</tr>

<tr>
<td align="center">2019</td>
<td align="right">2</td>
<td align="center">2006</td>
<td align="right">13</td>
</tr>

<tr>
<td align="center">2018</td>
<td align="right">1</td>
<td align="center">2005</td>
<td align="right">8</td>
</tr>

<tr>
<td align="center">2016</td>
<td align="right">1</td>
<td align="center">2004</td>
<td align="right">19</td>
</tr>

<tr>
<td align="center">2013</td>
<td align="right">1</td>
<td align="center">2003</td>
<td align="right">16</td>
</tr>

<tr>
<td align="center">2012</td>
<td align="right">2</td>
<td align="center">2002</td>
<td align="right">12</td>
</tr>

<tr>
<td align="center">2011</td>
<td align="right">3</td>
<td align="center">2001</td>
<td align="right">7</td>
</tr>

<tr>
<td align="center">2010</td>
<td align="right">5</td>
<td align="center"></td>
<td align="right"></td>
</tr>
</tbody>
</table>

<p>But there were no releases from 2013 until 2016. Also, there was a gap in 2017. There was significant development done during those years without releases. But only those willing to run code from the repository were able to benefit.  In 2018 I took over as release manager and chose to start doing yearly releases.</p>

<h2 id="why-release" id="why-release">Why Release</h2>

<p>With opensource projects, there are many reasons to release:

* get features/bug fixes to users
* thank contributors
* increase project visibility
* establish expectations
* provide deadlines</p>

<p>As I mentioned above, producing a release makes the work of contributors (developers, documentation writers, people reporting issues) isn&#39;t generally available. Frequent releases align with my preference to reduce <a href="https://en.wikipedia.org/wiki/Work_in_process" rel="nofollow">WIP (work in progress)</a>. Thanking the contributors by releasing their work helps drive enthusiasm and engagement.</p>

<p>Announcements on multiple platforms accompany each release. When I see another issue tracking/bug reporting program release announcement, I experience <a href="https://www.merriam-webster.com/dictionary/FOMO" rel="nofollow">FOMO</a> over the marketing provided by their announcement.</p>

<p>A regular release cadence helps users to schedule time for testing or upgrading the software. Also, regularly scheduled releases encourage  developers to finish their work and get it into a release. This deadline also drives me to go through all the steps to generate a beta and final release. This year (2023) will be my 6th yearly release.</p>

<h2 id="when-to-release" id="when-to-release">When to Release</h2>

<p>Although I do believe that reducing WIP is important, Roundup is not provided as a SAS (software as a service). It is deployed on-prem and meant to be customized. Some changes to Roundup require changes to a customized installation as part of the upgrade. For example:</p>
<ul><li>upgrade from Python 2 to Python 3 can require rewriting a tracker&#39;s business logic</li>
<li>support for newer databases versions can require table alteration (sometimes manual, sometimes programmatic using the <code>roundup-admin migrate</code> command)</li>
<li>security improvements and CSRF protection require changes to customized HTML templates.</li></ul>

<p>I mentioned my FOMO when seeing other products release multiple times a year. I often see releases with 5 or so changes.  Often these changes fix cosmetic issues or broken fields in the web interface.</p>

<p>With Roundup, many users have redesigned/replaced part of the interface. A release only supplies changes to the reference interfaces. The Roundup administrator needs to merge the reference changes into their production interface. In Roundup, changes can be implemented in production without requiring a release. This separation of concerns between the Roundup core and a customizable tracker makes Roundup more flexible. But, increases the work required during an upgrade.</p>

<p>Because of how customizable Roundup is, I try to balance between WIP and the churn caused by frequent releases. Yearly seems to be a sweet spot. There are occasions when a new core feature needs a change/bug fix. In that case the user has a choice to wait for a new release or use the most recent commit to the development repository.</p>

<p>If Roundup was a SAS with more limited customizability, we could do canary deploys directly from the repository. This would positively affect:</p>
<ul><li>getting features/bug fixes to users</li>
<li>thank contributors</li></ul>

<p>but would also reduce the utility of Roundup. Setting up a <a href="https://gettingthingsdone.com/" rel="nofollow">Getting Things Done</a> tracker for his/her family would find it very difficult in a SAS based model. Also, the tempo of releases in a SAS based model may not align with the user&#39;s needs for a stable platform.</p>

<p>If Shakespeare were a software writer would he have penned “Would software by another release schedule smell as sweet?”. Leave your thoughts in the comments.</p>

<p>(<a href="https://unsplash.com/photos/P1kP-BfIelI" rel="nofollow">Cover Image by Glen Carrie</a>)</p>

<p><a href="/rouilj/tag:webdev" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">webdev</span></a>, <a href="/rouilj/tag:discuss" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">discuss</span></a>, <a href="/rouilj/tag:opensource" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">opensource</span></a>, <a href="/rouilj/tag:development" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">development</span></a></p>
]]></content:encoded>
      <guid>https://blog.rouilj.dynamic-dns.net/rouilj/to-release-or-not-to-release-whats-a-good-schedule</guid>
      <pubDate>Mon, 12 Jun 2023 16:58:42 +0000</pubDate>
    </item>
    <item>
      <title>Can a Details Element be Used as an Accessible Popup (Without JavaScript)? </title>
      <link>https://blog.rouilj.dynamic-dns.net/rouilj/can-a-details-element-be-used-as-an-accessible-popup-without-javascript</link>
      <description>&lt;![CDATA[&#xA;&#xA;I tried a little experiment to make a popup using a details element. The goal was to see if I could make a clickable popup without using JavaScript. It should be accessible for both sighted users and users using a screen reader like NVDA.&#xA;&#xA;The HTML test case is a form with an info icon (the letter I in a circular blue field) after an input element. It is straightforward semantic HTML. The details element has a summary as its first element and a div enclosing paragraphs as the details&#39; body. The summary element is the letter I styled using CSS to put the I (for info) on a circular lightblue field. The form looks like:&#xA;&#xA;Image showing detail popup closed. The circular icon with an I in it used to open the popup has a light blue background color indicating it is closed. The rest of the screen displays labels, input fields, and one button at the bottom as you would see in a typical form.&#xA;&#xA;!--more--&#xA;&#xA;The HTML is:&#xA;  body&#xA;    form&#xA;      div&#xA;&#x9;spanlabel for=&#34;a&#34;A label:/label input id=&#34;a&#34;&#xA;&#x9;  details class=&#34;inline&#34; aria-live=&#34;polite&#34;&#xA;&#x9;    summary aria-label=&#34;Activate for info on A label.&#34;I!--&lt;img src=&#34;logo.png&#34;--  /summary&#xA;&#x9;    div&#xA;&#x9;      p&#xA;&#x9;&#x9;Here is a popup/popover with some interesting information&#xA;&#x9;&#x9;about the bA label/b input field. It might also be very&#xA;&#x9;&#x9;boring. The choice is up to you. But I would make it&#xA;&#x9;&#x9;interesting if I were you.&#xA;&#x9;      /p&#xA;&#x9;      pemUse space to close./em/p&#xA;&#x9;    /div&#xA;&#x9;  /details&#xA;&#x9;/span&#xA;&#x9;spanlabel for=&#34;b&#34;B label:/label input id=&#34;b&#34;&#xA;      /div&#xA;      div&#xA;      &#x9;spanlabel for=&#34;c&#34;C label:/label input id=&#34;c&#34;/span&#xA;...&#xA;  /form&#xA;/body&#xA; &#xA;There is a small sprinkling of ARIA. The summary field&#39;s aria-label announces what happens when you open the details element. Originally I added aria-live=&#34;polite&#34; on the div after the summary. But the detailed text was not announced when the details element opened. Moving aria-live to the details element, announced the text automatically when opened.&#xA;&#xA;The summary icon trigger is opened by:&#xA;&#xA;  clicking on the icon with the mouse,&#xA;  focusing on the icon (using tab) and&#xA;    hitting space or&#xA;    hitting return&#xA;&#xA;Unlike other popups, this is triggered by a focusing user action rather than hover so it works on mobile as well.&#xA;&#xA;The CSS was also rather straightforward. Changing the detail element&#39;s display to inline-block (default block) keeps it on the same line as the input. Changing the summary item&#39;s display to inline-block (default list-item) removes the open/close arrow associated with the details summary.&#xA;&#xA;The icon was formed by using border-radius and background-color. To get things to align, I set the custom property --icon-size to the line height of the page. This property then gets used to square up (circle up 8-)) the shape of the summary text icon. You can use an image as well if you want. The background of the icon changes when opened, and can be seen through transparent parts of an image.&#xA;&#xA;The details element (with the inline class) has its position set to relative. This makes positioning the div containing the detailed text easier. The div itself uses position: absolute. With these settings, the attributes left, top, right, and bottom can be used to shift the div relative to its default location.&#xA;&#xA;This is the complete CSS:&#xA;/ styles for details popup /&#xA;:root { --icon-size: calc(1.5em); / line height / }&#xA;details.inline {&#xA;  display: inline-block;&#xA;  position: relative; / anchor popup position /&#xA;}&#xA;details.inline summary {&#xA;  background-color: lightblue;&#xA;  border: 1px black solid;&#xA;  border-radius: 50%;&#xA;  cursor: default;&#xA;  display: inline-block;  / remove marker /&#xA;  font-weight: bold;&#xA;  height: var(--icon-size); / make icon round /&#xA;  text-align: center;&#xA;  width:var(--icon-size); / make icon round /&#xA;}&#xA;details.inline summary img {&#xA;  / if you want to use an image /&#xA;  height: var(--icon-size);&#xA;  width: var(--icon-size);&#xA;}&#xA;details.inline   div   {&#xA;  margin-block: 0.25em;&#xA;  padding-inline: 0.25em;&#xA;}&#xA;details.inline[open] summary { background-color: yellow; }&#xA;details.inline[open]   div {&#xA;  background-color: white;&#xA;  border: 2px inset black;&#xA;  border-radius: 1em;&#xA;  left: -10ch; / shift box to left /&#xA;  / Move the popup box after the icon with&#xA;     a 5px gap /&#xA;  / left: calc(var(--icon-size) + 5px); /&#xA;  position: absolute;&#xA;  / To align first line of popup with the baseline of the icon:&#xA;   shift up by 1/3 of the line height (--icon-size  -0.3333)&#xA;   then shift down by the margin-block on the div (+ 0.25em).&#xA;   This turns out to be 0.25em. /&#xA;  /top: calc((var(--icon-size)  -0.333) + 0.25em);*/&#xA;  width: 40ch;&#xA;}&#xA;&#xA;The open popup looks like this:&#xA;Image showing detail popup open. The circular icon with an I in it used to open/close the popup has a yellow background color indicating it is open. The popup sits on top of other text on the page. It is shifted to the left under the opening icon.&#xA;&#xA;Prettier styling is an exercise for the reader 8-).&#xA;&#xA;It can be enhanced with JavaScript to:&#xA;&#xA;  close with a keypress (using the escape key for example)&#xA;  close with a mouse click outside of the popup&#xA;&#xA;Even without JavaScript, it is usable in Firefox and Chrome (which includes browsers like Edge, Brave, Vivaldi...).  The details element should be accessible out of the box since it is a native HTML control. Although I only tested with NVDA and Windows Narrator, I expect it will work with other screen readers as well. (Note that Narrator would sometimes refuse to read the popup when opened. I have not discovered what causes it to refuse to read the popup on open. Moving the cursor with the right arrow key causes it to read the popup, and pressing the space key still closes the popup so....)&#xA;&#xA;This seems to be a successful experiment. Custom variables, details element, and calc are all well supported. This technique should work on the majority of browsers out there today.&#xA;&#xA;This technique will probably make it into some of the trackers I design for the Roundup Issue Tracker.&#xA;&#xA;#HTML #DevDotTo #WebDev #a11y]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vqykbxskdtfy142vxml3.png" alt="" title="Banner image"></p>

<p>I tried a little experiment to make a popup using a details element. The goal was to see if I could make a clickable popup without using JavaScript. It should be accessible for both sighted users and users using a screen reader like <a href="https://www.nvaccess.org/" rel="nofollow">NVDA</a>.</p>

<p>The HTML test case is a form with an info icon (the letter I in a circular blue field) after an input element. It is straightforward semantic HTML. The details element has a summary as its first element and a div enclosing paragraphs as the details&#39; body. The summary element is the letter <code>I</code> styled using CSS to put the <code>I</code> (for info) on a circular lightblue field. The form looks like:</p>

<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8da255vyr93gkoivp3ak.png" alt="Image showing detail popup closed. The circular icon with an I in it used to open the popup has a light blue background color indicating it is closed. The rest of the screen displays labels, input fields, and one button at the bottom as you would see in a typical form."></p>



<p>The HTML is:</p>

<pre><code class="language-html">  &lt;body&gt;
    &lt;form&gt;
      &lt;div&gt;
	&lt;span&gt;&lt;label for=&#34;a&#34;&gt;A label:&lt;/label&gt; &lt;input id=&#34;a&#34;&gt;
	  &lt;details class=&#34;inline&#34; aria-live=&#34;polite&#34;&gt;
	    &lt;summary aria-label=&#34;Activate for info on A label.&#34;&gt;I&lt;!--&lt;img src=&#34;logo.png&#34;&gt;--&gt;&lt;/summary&gt;
	    &lt;div&gt;
	      &lt;p&gt;
		Here is a popup/popover with some interesting information
		about the &lt;b&gt;A label&lt;/b&gt; input field. It might also be very
		boring. The choice is up to you. But I would make it
		interesting if I were you.
	      &lt;/p&gt;
	      &lt;p&gt;&lt;em&gt;Use space to close.&lt;/em&gt;&lt;/p&gt;
	    &lt;/div&gt;
	  &lt;/details&gt;
	&lt;/span&gt;
	&lt;span&gt;&lt;label for=&#34;b&#34;&gt;B label:&lt;/label&gt; &lt;input id=&#34;b&#34;&gt;
      &lt;/div&gt;
      &lt;div&gt;
      	&lt;span&gt;&lt;label for=&#34;c&#34;&gt;C label:&lt;/label&gt; &lt;input id=&#34;c&#34;&gt;&lt;/span&gt;
...
  &lt;/form&gt;
&lt;/body&gt;
</code></pre>

<p>There is a small sprinkling of <a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA" rel="nofollow">ARIA</a>. The summary field&#39;s <code>aria-label</code> announces what happens when you open the details element. Originally I added <code>aria-live=&#34;polite&#34;</code> on the <code>div</code> after the <code>summary</code>. But the detailed text was not announced when the details element opened. Moving <code>aria-live</code> to the details element, announced the text automatically when opened.</p>

<p>The summary icon trigger is opened by:</p>
<ul><li>clicking on the icon with the mouse,</li>
<li>focusing on the icon (using tab) and
<ul><li>hitting space or</li>
<li>hitting return</li></ul></li></ul>

<p>Unlike other popups, this is triggered by a focusing user action rather than hover so it works on mobile as well.</p>

<p>The CSS was also rather straightforward. Changing the detail element&#39;s <code>display</code> to <code>inline-block</code> (default <code>block</code>) keeps it on the same line as the input. Changing the summary item&#39;s <code>display</code> to <code>inline-block</code> (default <code>list-item</code>) removes the open/close arrow associated with the details summary.</p>

<p>The icon was formed by using <code>border-radius</code> and <code>background-color</code>. To get things to align, I set the custom property <code>--icon-size</code> to the line height of the page. This property then gets used to square up (circle up 8-)) the shape of the summary text icon. You can use an image as well if you want. The background of the icon changes when opened, and can be seen through transparent parts of an image.</p>

<p>The details element (with the <code>inline</code> class) has its <code>position</code> set to <code>relative</code>. This makes positioning the div containing the detailed text easier. The div itself uses <code>position: absolute</code>. With these settings, the attributes <a href="https://css-tricks.com/almanac/properties/t/top-right-bottom-left/" rel="nofollow"><code>left</code>, <code>top</code>, <code>right</code>, and <code>bottom</code></a> can be used to shift the div relative to its default location.</p>

<p>This is the complete CSS:</p>

<pre><code class="language-css">/* styles for details popup */
:root { --icon-size: calc(1.5em); /* line height */ }
details.inline {
  display: inline-block;
  position: relative; /* anchor popup position */
}
details.inline summary {
  background-color: lightblue;
  border: 1px black solid;
  border-radius: 50%;
  cursor: default;
  display: inline-block;  /* remove marker */
  font-weight: bold;
  height: var(--icon-size); /* make icon round */
  text-align: center;
  width:var(--icon-size); /* make icon round */
}
details.inline summary img {
  /* if you want to use an image */
  height: var(--icon-size);
  width: var(--icon-size);
}
details.inline &gt; div &gt; * {
  margin-block: 0.25em;
  padding-inline: 0.25em;
}
details.inline[open] summary { background-color: yellow; }
details.inline[open] &gt; div {
  background-color: white;
  border: 2px inset black;
  border-radius: 1em;
  left: -10ch; /* shift box to left */
  /* Move the popup box after the icon with
     a 5px gap */
  /* left: calc(var(--icon-size) + 5px); */
  position: absolute;
  /* To align first line of popup with the baseline of the icon:
   * shift up by 1/3 of the line height (--icon-size * -0.3333)
   * then shift down by the margin-block on the div (+ 0.25em).
   * This turns out to be 0.25em. */
  /*top: calc((var(--icon-size) * -0.333) + 0.25em);*/
  width: 40ch;
}
</code></pre>

<p>The open popup looks like this:
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vqykbxskdtfy142vxml3.png" alt="Image showing detail popup open. The circular icon with an I in it used to open/close the popup has a yellow background color indicating it is open. The popup sits on top of other text on the page. It is shifted to the left under the opening icon."></p>

<p>Prettier styling is an exercise for the reader 8-).</p>

<p>It can be enhanced with JavaScript to:</p>
<ul><li>close with a keypress (using the escape key for example)</li>
<li>close with a mouse click outside of the popup</li></ul>

<p>Even without JavaScript, it is usable in Firefox and Chrome (which includes browsers like Edge, Brave, Vivaldi...).  The details element should be accessible out of the box since it is a native HTML control. Although I only tested with NVDA and Windows Narrator, I expect it will work with other screen readers as well. (Note that Narrator would sometimes refuse to read the popup when opened. I have not discovered what causes it to refuse to read the popup on open. Moving the cursor with the right arrow key causes it to read the popup, and pressing the space key still closes the popup so....)</p>

<p>This seems to be a successful experiment. Custom variables, details element, and calc are all well supported. This technique should work on the majority of browsers out there today.</p>

<p>This technique will probably make it into some of the trackers I design for the <a href="https://www.roundup-tracker.org/?ref=dev.to" rel="nofollow">Roundup Issue Tracker</a>.</p>

<p><a href="/rouilj/tag:HTML" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">HTML</span></a> <a href="/rouilj/tag:DevDotTo" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">DevDotTo</span></a> <a href="/rouilj/tag:WebDev" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">WebDev</span></a> <a href="/rouilj/tag:a11y" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">a11y</span></a></p>
]]></content:encoded>
      <guid>https://blog.rouilj.dynamic-dns.net/rouilj/can-a-details-element-be-used-as-an-accessible-popup-without-javascript</guid>
      <pubDate>Sun, 05 Mar 2023 21:17:06 +0000</pubDate>
    </item>
    <item>
      <title>Mobile Web Page Debugging Without a Desktop or USB Cable</title>
      <link>https://blog.rouilj.dynamic-dns.net/rouilj/mobile-web-page-debugging-without-a-desktop-or-usb-cable</link>
      <description>&lt;![CDATA[img src=&#34;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bkoel2wt5oi2ia91atau.png&#34; height=520px title=&#34;Banner image&#34;&#xA;&#xA;One of the things I have to do when developing a new tracker for the Roundup Issue Tracker is test on mobile browsers. When I run into issues, I used to hook up a remote debugger. This usually requires finding a USB cable as wifi is less reliable.&#xA;&#xA;I really wish that mobile browsers had a native console that I could use to see console.log output and javascript errors. While not native, MobileConsole by hnldesign provides quite a bit of power by including one script and one style tag in my HTML page. At 45k uncompressed (17k gzipped) it&#39;s sized between the smallest and largest mobile consoles I found.&#xA;!--more--&#xA;It can be seen at the bottom of the web page:&#xA;&#xA;Phone screenshot showing the MobileConsole displayed on the bottom half of the screen. The top half displays a web page. There is a movable divider between the two. The console is showing log messages from the page along with the line number where they were logged&#xA;&#xA;It supports the usual console output functions (error, ... debug). Plus it supports console.time/timeEnd and console.trace along with the group family. It also displays objects collapsed and allows you to toggle them open. The console can be resized allowing you to reduce the screen real-estate used on a mobile device.&#xA;&#xA;It has some areas for improvement:&#xA;   it only opens objects one level deep. So an array in an object is displayed but you can&#39;t expand the array further to get access to its elements.&#xA;   grouping is not nested or collapsible. But this is noted as an area for improvement.&#xA;&#xA;If you do a lot of logging, you might want to download the library, open your editor, and apply a change that reduces redraws triggered by console output.&#xA;&#xA;One thing to note is that running commands in the console will not work if you are using a Content Security Policy (CSP). Any CSP that does not allow &#39;unsafe-eval&#39; will prevent console commands from running. But at least you will see the error in the console 8-).&#xA;&#xA;There are other mobile consoles as well:&#xA;&#xA;   mobileConsole is the smallest at 5k, but appears to only support console.log. Documentation is sparse and there is no demo.&#xA;   B1naryStudio js-mobile-console - is 6k in size. It has methods to show/hide, enable auto-opening on error, and create command aliases.&#xA;However, in its demo, I had issues with it displaying document.querySelect(&#39;body&#39;). All it showed was an empty object {}.&#xA;   Eruda is a console with a size of 130k gzipped. However, with that size, you get an element browser, network monitor, and other tools you know from your desktop browser&#39;s DevTools. It also has a plugin ecosystem. It properly handles console.group as a toggle. Also you can submit multi-line scripts. I think the only thing Eruda is missing is a debugger 8-).&#xA;&#xA;Using hnldesign&#39;s MobileBrowser I was able to figure out why my javascript was crashing. If your hardware can handle it though, Eruda has many more features to make debugging on mobile device easier. Now the only time I need to find a USB cable is when I have to debug using breakpoints.&#xA;&#xA;#WebDev #DevDotTo #OpenSource, #javascript&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bkoel2wt5oi2ia91atau.png" title="Banner image"></p>

<p>One of the things I have to do when developing a new tracker for the <a href="https://www.roundup-tracker.org?ref=dev_to" rel="nofollow">Roundup Issue Tracker</a> is test on mobile browsers. When I run into issues, I used to hook up a remote debugger. This usually requires finding a USB cable as wifi is less reliable.</p>

<p>I really wish that mobile browsers had a native console that I could use to see <code>console.log</code> output and javascript errors. While not native, <a href="https://github.com/c-kick/mobileConsole" rel="nofollow">MobileConsole by hnldesign</a> provides quite a bit of power by including one script and one style tag in my HTML page. At 45k uncompressed (17k gzipped) it&#39;s sized between the smallest and largest mobile consoles I found.

It can be seen at the bottom of the web page:</p>

<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jmkbsekrqfzsmuy1v75p.png" alt="Phone screenshot showing the MobileConsole displayed on the bottom half of the screen. The top half displays a web page. There is a movable divider between the two. The console is showing log messages from the page along with the line number where they were logged"></p>

<p>It supports the usual console output functions (<code>error</code>, ... <code>debug</code>). Plus it supports <code>console.time</code>/<code>timeEnd</code> and <code>console.trace</code> along with the <code>group</code> family. It also displays objects collapsed and allows you to toggle them open. The console can be resized allowing you to reduce the screen real-estate used on a mobile device.</p>

<p>It has some areas for improvement:
   * it only opens objects one level deep. So an array in an object is displayed but you can&#39;t expand the array further to get access to its elements.
   * grouping is not nested or collapsible. But this is noted as an area for improvement.</p>

<p>If you do a lot of logging, you might want to download the library, open your editor, and apply <a href="https://github.com/c-kick/mobileConsole/issues/2" rel="nofollow">a change that reduces redraws triggered by console output</a>.</p>

<p>One thing to note is that running commands in the console will not work if you are using a Content Security Policy (CSP). Any CSP that does not allow <code>&#39;unsafe-eval&#39;</code> will prevent console commands from running. But at least you will see the error in the console 8-).</p>

<p>There are other mobile consoles as well:</p>
<ul><li><a href="https://github.com/ljcucc/mobileConsole" rel="nofollow">mobileConsole</a> is the smallest at 5k, but appears to only support console.log. Documentation is sparse and there is no demo.</li>
<li><a href="https://github.com/B1naryStudio/js-mobile-console" rel="nofollow">B1naryStudio js-mobile-console</a> – is 6k in size. It has methods to show/hide, enable auto-opening on error, and create command aliases.
However, in its demo, I had issues with it displaying <code>document.querySelect(&#39;body&#39;)</code>. All it showed was an empty object <code>{}</code>.</li>
<li><a href="https://github.com/liriliri/eruda" rel="nofollow">Eruda</a> is a console with a size of 130k gzipped. However, with that size, you get an element browser, network monitor, and other tools you know from your desktop browser&#39;s DevTools. It also has a plugin ecosystem. It properly handles console.group as a toggle. Also you can submit multi-line scripts. I think the only thing Eruda is missing is a debugger 8-).</li></ul>

<p>Using hnldesign&#39;s MobileBrowser I was able to figure out why my javascript was crashing. If your hardware can handle it though, Eruda has many more features to make debugging on mobile device easier. Now the only time I need to find a USB cable is when I have to debug using breakpoints.</p>

<p><a href="/rouilj/tag:WebDev" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">WebDev</span></a> <a href="/rouilj/tag:DevDotTo" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">DevDotTo</span></a> <a href="/rouilj/tag:OpenSource" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">OpenSource</span></a>, <a href="/rouilj/tag:javascript" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">javascript</span></a></p>
]]></content:encoded>
      <guid>https://blog.rouilj.dynamic-dns.net/rouilj/mobile-web-page-debugging-without-a-desktop-or-usb-cable</guid>
      <pubDate>Mon, 20 Feb 2023 06:53:04 +0000</pubDate>
    </item>
    <item>
      <title>Implementing a Command Palette and Task Timer</title>
      <link>https://blog.rouilj.dynamic-dns.net/rouilj/implementing-a-command-palette-and-task-timer</link>
      <description>&lt;![CDATA[&#xA;I am a developer for the open source Roundup Issue Tracker. It has many use cases. One is to develop issue trackers like GitHub Issues, Bugzilla, or Request Tracker. I also develop a custom issue tracker for a help desk environment. This article describes the steps in adding a task-timing feature for that tracker.&#xA;&#xA;A user requested a task timer. The workflow:&#xA;&#xA;  open an issue page,&#xA;  start a timer,&#xA;  start the task associated with the issue,&#xA;  use the issue page to document the work.&#xA; &#xA;When done, the user would:&#xA;&#xA;  stop the timer,&#xA;  finish documentation, attach files, and make other changes to the issue,&#xA;  submit the time and other changes. &#xA;&#xA;There are a few decisions to make:&#xA;&#xA;  timer functions&#xA;     is start/stop enough?&#xA;     is pause/restart needed?&#xA;     does the user need to change/set/edit the timer?&#xA;     do we need to track seconds, minutes, and hours? Since the customer is billed for the time, recording seconds seems excessive. Is there a scenario where the timer would need to record seconds?&#xA;  timer controls&#xA;     do we use a button/buttons? Does checking a checkbox activate the timer?&#xA;     what does the UI look like for each timer function?&#xA;        Are the controls in the issue page?&#xA;        Are the controls in a floating popup? Does the popup need to be movable/collapsible&#xA;&#x9;so it doesn&#39;t block access to the underlying issue?&#xA;  notification feedback&#xA;     how is the user notified that the timer is running/paused/stopped?&#xA;     how does the user see the elapsed time?&#xA;  future planning&#xA;     how to add controls without cluttering an already complex interface&#xA;     what impact will this have on adding future feature requests in the same context&#xA;     does this guide us in implementing future workflows&#xA;&#xA;!--more--&#xA;Evaluating a Command Palette&#xA;&#xA;I decided to try to install a command palette. A command palette is a UI interface usually activated by a hotkey. It allows searching and selecting from a context-sensitive list of commands. You might be familiar with the VS Code command palette. Command palettes have many advantages for users:&#xA;&#xA;   discover useful commands (and their shortcuts)&#xA;   faster than scrolling down a long list of commands&#xA;   keyboard is faster than using the mouse&#xA;   invisible until activated&#xA;   make commands available that wouldn&#39;t be important enough to get a button or other UI element&#xA;&#xA;There are many command palette implementations in JavaScript. Some are used with specific frameworks. For example, kbar is a React component and spotlight is a Laravel component. I wanted one that would work with Vanilla JavaScript. I identified two candidates:&#xA;&#xA;  command-pal - &#34;The hackable command palette for the web, inspired by Visual Studio Code.&#34;&#xA;  Ninja Keys - &#34;Keyboard shortcut interface for your website that works with Vanilla JS, Vue, and React.&#34;&#xA;&#xA;Ninja Keys uses Lit while command-pal uses Svelte. I don&#39;t have any experience with either, so.... Both of them can bind any command on the palette to a hotkey thanks to hotkeys.js. Both are MIT licensed. Command-pal is larger in size, but it also bundles all the libraries it needs. It looks like Ninja Keys loads libraries/modules on demand from CDNs on the internet. Being able to use the library without internet access is a nice feature.&#xA;&#xA;Command-pal promotes itself as &#34;hackable&#34;. This usually means flexibility and sometimes simplicity. I like both. Command-pal&#39;s feature set wasn&#39;t as impressive as Ninja Keys. But it does include a floating button to trigger the command palette on mobile. This is a nice touch as moving the page to access UI elements can be tedious on mobile.&#xA;&#xA;Adding command-pal&#xA;&#xA;As a result, I chose command-pal. Integrating it was easy. I downloaded the file from the CDN. I also downloaded the dark theme from GitHub. I added a script tag and stylesheet link to the top-level page Roundup template file. This makes command-pal available on all the tracker&#39;s pages.&#xA;&#xA;To invoke it, I added:&#xA;&#xA; const c = new CommandPal({&#xA;   hotkey: &#34;ctrl+space&#34;,&#xA;   hotkeysGlobal: true,&#xA;   commands: commands,&#xA; });&#xA; c.start();&#xA;inside a script tag. The tracker is a web application. Sadly, the classic command palette hotkeys: &#34;ctrl+k&#34; or &#34;ctrl+shift+p&#34; are already used by the browser. I also wanted to activate the palette using the hotkey when focused on an input, select, or textarea. hotkeysGlobal: true should do that, but it didn&#39;t work for me in Firefox, Chrome, or Brave). I submitted a pull request to fix it.&#xA;&#xA;The commands array included:&#xA;&#xA;  [&#xA;    {&#xA;      name: &#34;Exit Command Palette&#34;,&#xA;      contexts: [ &#34;all&#34; ],&#xA;      weight: -10,&#xA;    },&#xA;    {&#xA;      name: &#34;Initial Screen&#34;,&#xA;      description: &#34;Screen shown after login.&#34;,&#xA;      contexts: [ &#34;all&#34; ],&#xA;      handler: () =  (window.location.href = &#34;.&#34;),&#xA;      shortcut: &#34;ctrl+i&#34;,&#xA;      weight: 5,&#xA;    }, ...&#xA;]&#xA;Besides the fields used by command-pal:&#xA;&#xA;  name,&#xA;  description,&#xA;  handler,&#xA;  shortcut&#xA;&#xA;I added extra fields:&#xA;&#xA;  weight&#xA;  contexts&#xA;&#xA;These were inspired by the Superhuman blog on building a remarkable command palette. Among the things they suggest are:&#xA;&#xA;   order commands by popularity/utility&#xA;   listed commands are context sensitive&#xA;&#xA;The commands are initially sorted by weight (using commands.sort()). This displays the most popular (highest weight) commands at the top of the menu. Displaying the search results sorted by weight is an ongoing project.&#xA;&#xA;The command is shown if its contexts property matches the current context. For example, the task timing commands only make sense when editing an issue. They should not be shown when viewing a list of issues, or a user&#39;s profile page. Inspecting the page&#39;s URL determines the current context. The code filters the command list, eliminating commands that are not appropriate for the context. Only then is CommandPal invoked.&#xA;&#xA;details&#xA;summary Originally written as a 2 part series. To see the transition click here or just continue reading...&#xA;/summary&#xA;I hope you have enjoyed learning about command palettes and command-pal in particular. In part 2, we will use command-pal to control the task timer and look at how it integrates with the tracker built using the Roundup Issue Tracker.&#xA;{% embed https://www.roundup-tracker.org %}&#xA;&#xA;!--article-break--&#xA;I am a developer for the open source Roundup Issue Tracker. It has many use cases. One is to develop issue trackers like GitHub Issues, Bugzilla, or Request Tracker. I also develop a custom issue tracker for a help desk environment. This article continues with the steps to add a task-timing feature for that tracker.&#xA;&#xA;In part 1 of this series I had just finished installing command-pal. Let&#39;s take a closer look at command-pal before we get to the timer.&#xA;/details&#xA;&#xA;Enhancing command-pal and Handling a Showstopper&#xA;&#xA;The  Superhuman blog post  lists other desirable features for a command palette:  &#xA;&#xA;  fuzzy search (for mispelings 8-)) - is included in command-pal using fuse.js. (It looks like there is a fork of Ninja Keys that has fuzzy search support.)&#xA;  icons - I created an issue and pull request to add support&#xA;  synonyms - the fuzzy search includes the description field. This helps broaden the matching terms. But a description shouldn&#39;t be a keyword/synonym list. fuse.js can search an array of strings that are part of an object. Adding this functionality is a work in progress.&#xA;&#xA;One interesting possibility is supporting multiple command palettes on a page. Each palette would have a different set of commands.  I am not sure that&#39;s a good idea. Superhuman suggests making the command palette omnipotent. Multiple palettes force the user to make a decision about which palette to activate. This breaks the idea of &#34;don&#39;t make me think&#34;.  I was able to create and activate multiple palettes with different hotkeys. However, more work on supporting multiple palettes on a page is needed.&#xA;&#xA;At its core, a command palette is a large select modal. Having the ability to activate the modal from Javascript could allow the palette to be used in more places without making the user think.&#xA;&#xA;I am pleased with command-pal. I have found it quite hackable even though I have never used Svelte before. However, I did have one potential showstopper.&#xA;&#xA;The tracker uses a Content Security Policy (CSP). Style blocks in the page include a nonce. If the nonce is missing or doesn&#39;t match the one in the CSP the style blocks are ignored. Svelte generates style blocks for each element that it creates/injects. These client-side blocks, don&#39;t have access to the server&#39;s CSP nonce. If they did have access to the nonce, the nonce would be useless for securing the page&#39;s assets. If the style blocks were in a file that could be fetched using a stylesheet link, everything would be fine. However, efforts to do this with Svelt have failed. Another alternative is to generate a secure hash (sha256, 384, or 512). How to get this generated at build time is unclear. However, I did find a way to calculate it at runtime that seems to work. I proposed a patch to allow an administrator to generate the hashes using the command-pal library.&#xA;&#xA;The mechanism for controlling the task timer is done. Now to turn my attention to actually timing tasks.&#xA;&#xA;Let&#39;s Time All The Things&#xA;&#xA;I chose the easytimer.js library. It supports:&#xA;&#xA;   setting an initial value to start counting time&#xA;   one minute timer granularity - to reduce CPU load&#xA;   pausing and restarting timers while keeping their accumulated time.&#xA; &#xA; The command palette allows the gross controls: stop/start/pause/resume. I still need to handle the other parts of the UI. The existing issue page provides a field for manually entering the task time. Rather than trying to create a new UI for the timer, I reused the existing field. The UI is relatively simple. There is a &#34;Time spent&#34; input element referred to as the &#34;time element&#34; below. &#xA;&#xA;  If the user prefills the time element with a number of minutes, the timer will start counting up from that time. This is helpful if you forgot to start the timer and start it after say 10 minutes.&#xA;  The use case only requires 1 minute precision. Since I am counting in minutes, I add one minute to the start time. This rounds the time up to the next minute.&#xA;  The time element is updated only once a minute. This is great for reducing CPU use, but poor for user feedback. There needs to be some way to notify the user that the timer is running. This needs to work without interfering with the ability to use the rest of the issue interface. Using a popup could work. But popups clutter the interface. If it can&#39;t be moved, it may hide something the user wants to use.&#xA;  Instead, I cycle the background color for the time element from yellow to goldenrod every 5 seconds. This is done using CSS rather than javascript. It should perform better than updating the input with a flashing indicator every second.&#xA;  The animation stops when the timer is paused. But the yellow background color is still shown in the time element.&#xA;  When the timer stops, the background of the time element returns to white.&#xA;  Besides the time element displaying state, other elements of the page change as well. Starting the timer makes a pending change to the issue. The issue page already has a mechanism for indicating a page with a pending change. Starting the timer triggers this mechanism. This results in:&#xA;    a change in the background color of the time element label (&#34;Time spent&#34;)&#xA;    a change in the background color for the H1 header on the page&#xA;    the H1 header on the page gets &#34;pending changes&#34; appended&#xA;    the title for the page has an exclamation mark prepended to it; allowing the page to be identified in a list of page titles&#xA;    the favicon for the page is overlayed with an exclamation mark inside a yellow dot; allowing the tab to be identified if the text can&#39;t be shown.&#xA;&#xA;The command-pal Javascript Entries&#xA;&#xA;Here are the three commands for working with the timer:&#xA;&#xA;// more commands&#xA;{&#xA;  name: &#34;Start or Unpause Timer&#34;,&#xA;  description: &#34;timer&#34;,&#xA;  contexts: [ &#34;issue.item&#34; ],  // only show timers on an issue page&#xA;  handler: () =  {&#xA;    if ( ! window.userTimer ) {&#xA;      try {&#xA;&#x9;// create the object&#xA;&#x9;window.userTimer = {&#xA;&#x9;  timer: new easytimer.Timer({precision: &#34;minutes&#34;}),&#xA;&#x9;  timeField: document.getElementById(&#34;time&#34;),&#xA;&#x9;  animation: null,&#xA;&#x9;}&#xA;      } catch (err) {&#xA;&#x9;alert(Error: ${err.name} - ${err.message})&#xA;      }&#xA;    }&#xA;&#xA;    let timer = window.userTimer.timer;&#xA;    let timeField = null;&#xA;    if ( window.userTimer.timeField ) {&#xA;      timeField = window.userTimer.timeField;&#xA;    } else {&#xA;      alert(&#34;Unable to find &#39;Time Spent&#39; field. Are you viewing an issue?&#34;);&#xA;      return;&#xA;    }&#xA;&#xA;    if ( timer.isRunning() ) {&#xA;      alert(&#34;Timer is running for: &#34; + &#xA;&#x9;    timer.getTimeValues())&#xA;      return;&#xA;    }&#xA;&#xA;    // restart timer&#xA;    if ( timer.isPaused() ) {&#xA;      timer.start();&#xA;      alert(&#34;Timer restarted at: &#34; + &#xA;&#x9;    timer.getTimeValues())&#xA;      window.userTimer.animation.play()&#xA;      return;&#xA;    }&#xA;&#xA;    // start timer instance&#xA;    let timeValue = parseInt(timeField.value);&#xA;    if (! timeValue || isNaN(timeValue)) { timeValue=0; }&#xA;    // round up time to next minute: 10 seconds -  1 minute&#xA;    let startValues = { minutes: timeValue + 1 }&#xA;&#xA;    timer.start({&#xA;      startValues: startValues,&#xA;      callback: function (timer) {&#xA;&#x9;let timeField = window.userTimer.timeField&#xA;&#x9;timeField.value = timer.getTotalTimeValues().minutes;&#xA;      }&#xA;    });&#xA;    alert(&#34;Timer started at: &#34; +&#xA;&#x9;  timer.getTimeValues())&#xA;    window.userTimer.animation = animatetimer(timeField);&#xA;&#xA;    timeField.value = timer.getTotalTimeValues().minutes;&#xA;&#xA;    // mark field/page with a pending change&#xA;    timeField.dispatchEvent(new Event(&#39;change&#39;,&#xA;&#x9;&#x9;&#x9;&#x9;      {bubbles: true}))&#xA;  },&#xA;  icon: &#39;span class=&#34;icon&#34;&amp;nbsp;⏱️ /span&#39;, // stopwatch emoji&#xA;  weight: 10, // sort this to the top of the list&#xA;},&#xA;{&#xA;  name: &#34;Pause Timer&#34;,&#xA;  contexts: [ &#34;issue.item&#34; ],&#xA;  description: &#34;Update time field and snooze the timer&#34;,&#xA;  handler: () =  {&#xA;    if (! (window.userTimer &amp;&amp; window.userTimer.timer) ) {&#xA;      alert(&#34;No timer was started.&#34;);&#xA;      return;&#xA;    }&#xA;&#xA;    let timer = window.userTimer.timer&#xA;    let timeField = window.userTimer.timeField&#xA;    let animation = window.userTimer.animation&#xA;&#xA;    if ( ! timer.isRunning() ) {&#xA;      alert(&#34;Timer is not running, time is: &#34; +&#xA;&#x9;    timer.getTimeValues());&#xA;      return;&#xA;    }&#xA;    timer.pause();&#xA;    animation.pause()&#xA;&#xA;    timeField.value = timer.getTotalTimeValues().minutes;&#xA;    alert(&#34;Timer paused at: &#34; +  timer.getTimeValues());&#xA;  },&#xA;},&#xA;{&#xA;  name: &#34;Stop Timer&#34;,&#xA;  contexts: [ &#34;issue.item&#34; ],&#xA;  handler: () =  {&#xA;    if (! (window.userTimer &amp;&amp; window.userTimer.timer) ) {&#xA;      alert(&#34;No timer was started.&#34;);&#xA;      return;&#xA;    }&#xA;&#xA;    let timer = window.userTimer.timer&#xA;&#xA;    if ( ! ( timer.isRunning() || timer.isPaused() )) {&#xA;      alert(&#34;Timer is not running, time is: &#34; +&#xA;&#x9;    timer.getTimeValues());&#xA;      return;&#xA;    }&#xA;&#xA;    let animation = window.userTimer.animation&#xA;    let timeField = window.userTimer.timeField&#xA;&#xA;    timer.pause(); // stop time update&#xA;    timeField.value = timer.getTotalTimeValues().minutes;&#xA;    timer.stop(); // also zero&#39;s timer.&#xA;    animation.cancel();&#xA;    window.userTimer.animation = null;&#xA;  },&#xA;},&#xA;// more commands&#xA;(Note: the version of command-pal that I am running has the change to support icons.)&#xA;&#xA;There is one helper function to set up the animation for the &#34;Time spent&#34; input field:&#xA;&#xA;function animatetimer(input) {&#xA;  return input.animate(&#xA;    [&#xA;      {backgroundColor: &#39;yellow&#39;, easing: &#39;linear&#39;},&#xA;      {backgroundColor: &#39;goldenrod&#39;, easing: &#39;linear&#39;},&#xA;      {backgroundColor: &#39;yellow&#39;, easing: &#39;linear&#39;}&#xA;    ],&#xA;    {duration: 5000, iterations: Infinity}&#xA;  );&#xA;}&#xA;&#xA;Working Example&#xA;&#xA;You can see this in action on the demo site. Use demo/demo for login. Activate the palette using ctrl+space. The code is also published.&#xA;&#xA;#javascript #opensource #DevDotTo #WebDev&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xejupqot8xa8g34r4iff.png" alt="" title="Banner image">
I am a developer for the open source <a href="https://www.roundup-tracker.org" rel="nofollow">Roundup Issue Tracker</a>. It has many use cases. One is to develop issue trackers like GitHub Issues, Bugzilla, or Request Tracker. I also develop a custom issue tracker for a help desk environment. This article describes the steps in adding a task-timing feature for that tracker.</p>

<p>A user requested a task timer. The workflow:</p>
<ul><li>open an issue page,</li>
<li>start a timer,</li>
<li>start the task associated with the issue,</li>
<li>use the issue page to document the work.</li></ul>

<p>When done, the user would:</p>
<ul><li>stop the timer,</li>
<li>finish documentation, attach files, and make other changes to the issue,</li>
<li>submit the time and other changes.</li></ul>

<p>There are a few decisions to make:</p>
<ul><li>timer functions
<ul><li>is start/stop enough?</li>
<li>is pause/restart needed?</li>
<li>does the user need to change/set/edit the timer?</li>
<li>do we need to track seconds, minutes, and hours? Since the customer is billed for the time, recording seconds seems excessive. Is there a scenario where the timer would need to record seconds?</li></ul></li>
<li>timer controls
<ul><li>do we use a button/buttons? Does checking a checkbox activate the timer?</li>
<li>what does the UI look like for each timer function?
Are the controls in the issue page?
Are the controls in a floating popup? Does the popup need to be movable/collapsible
so it doesn&#39;t block access to the underlying issue?</li></ul></li>
<li>notification feedback
<ul><li>how is the user notified that the timer is running/paused/stopped?</li>
<li>how does the user see the elapsed time?</li></ul></li>
<li>future planning
<ul><li>how to add controls without cluttering an already complex interface</li>
<li>what impact will this have on adding future feature requests in the same context</li>
<li>does this guide us in implementing future workflows</li></ul></li></ul>



<h2 id="evaluating-a-command-palette" id="evaluating-a-command-palette">Evaluating a Command Palette</h2>

<p>I decided to try to install a <a href="https://www.commandbar.com/blog/command-palette-past-present-and-future" rel="nofollow">command palette</a>. A command palette is a UI interface usually activated by a hotkey. It allows searching and selecting from a context-sensitive list of commands. You might be familiar with the <a href="https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette" rel="nofollow">VS Code command palette</a>. Command palettes have many advantages for users:</p>
<ul><li>discover useful commands (and their shortcuts)</li>
<li>faster than scrolling down a long list of commands</li>
<li>keyboard is faster than using the mouse</li>
<li>invisible until activated</li>
<li>make commands available that wouldn&#39;t be important enough to get a button or other UI element</li></ul>

<p>There are many command palette implementations in JavaScript. Some are used with specific frameworks. For example, <a href="https://github.com/timc1/kbar" rel="nofollow">kbar</a> is a React component and <a href="https://github.com/livewire-ui/spotlight" rel="nofollow">spotlight</a> is a Laravel component. I wanted one that would work with <a href="https://www.javatpoint.com/what-is-vanilla-javascript" rel="nofollow">Vanilla JavaScript</a>. I identified two candidates:</p>
<ul><li><a href="https://github.com/benwinding/command-pal" rel="nofollow">command-pal</a> – “The hackable command palette for the web, inspired by Visual Studio Code.”</li>
<li><a href="https://github.com/ssleptsov/ninja-keys" rel="nofollow">Ninja Keys</a> – “Keyboard shortcut interface for your website that works with Vanilla JS, Vue, and React.”</li></ul>

<p>Ninja Keys uses <a href="https://lit.dev/" rel="nofollow">Lit</a> while command-pal uses <a href="https://svelte.dev" rel="nofollow">Svelte</a>. I don&#39;t have any experience with either, so.... Both of them can bind any command on the palette to a hotkey thanks to <a href="https://github.com/jaywcjlove/hotkeys/" rel="nofollow">hotkeys.js</a>. Both are MIT licensed. Command-pal is larger in size, but it also bundles all the libraries it needs. It looks like Ninja Keys loads libraries/modules on demand from CDNs on the internet. Being able to use the library without internet access is a nice feature.</p>

<p>Command-pal promotes itself as “hackable”. This usually means flexibility and sometimes simplicity. I like both. Command-pal&#39;s feature set wasn&#39;t as impressive as Ninja Keys. But it does include a floating button to trigger the command palette on mobile. This is a nice touch as moving the page to access UI elements can be tedious on mobile.</p>

<h2 id="adding-command-pal" id="adding-command-pal">Adding command-pal</h2>

<p>As a result, I chose command-pal. Integrating it was easy. I downloaded the file from the <a href="https://cdn.jsdelivr.net/npm/command-pal" rel="nofollow">CDN</a>. I also downloaded the <a href="https://github.com/benwinding/command-pal/tree/master/public" rel="nofollow">dark theme</a> from GitHub. I added a script tag and stylesheet link to the top-level page Roundup template file. This makes command-pal available on all the tracker&#39;s pages.</p>

<p>To invoke it, I added:</p>

<pre><code class="language-js"> const c = new CommandPal({
   hotkey: &#34;ctrl+space&#34;,
   hotkeysGlobal: true,
   commands: commands,
 });
 c.start();
</code></pre>

<p>inside a <code>script</code> tag. The tracker is a web application. Sadly, the classic command palette hotkeys: “ctrl+k” or “ctrl+shift+p” are already used by the browser. I also wanted to activate the palette using the hotkey when focused on an input, select, or textarea. <code>hotkeysGlobal: true</code> should do that, but it didn&#39;t work for me in Firefox, Chrome, or Brave). I <a href="https://github.com/benwinding/command-pal/pull/18" rel="nofollow">submitted a pull request to fix it.</a></p>

<p>The commands array included:</p>

<pre><code>  [
    {
      name: &#34;Exit Command Palette&#34;,
      contexts: [ &#34;all&#34; ],
      weight: -10,
    },
    {
      name: &#34;Initial Screen&#34;,
      description: &#34;Screen shown after login.&#34;,
      contexts: [ &#34;all&#34; ],
      handler: () =&gt; (window.location.href = &#34;.&#34;),
      shortcut: &#34;ctrl+i&#34;,_
      weight: 5,
    }, ...
]
</code></pre>

<p>Besides the fields used by command-pal:</p>
<ul><li>name,</li>
<li>description,</li>
<li>handler,</li>
<li>shortcut</li></ul>

<p>I added extra fields:</p>
<ul><li>weight</li>
<li>contexts</li></ul>

<p>These were inspired by the <a href="https://blog.superhuman.com/how-to-build-a-remarkable-command-palette/" rel="nofollow">Superhuman blog on building a remarkable command palette</a>. Among the things they suggest are:</p>
<ul><li>order commands by popularity/utility</li>
<li>listed commands are context sensitive</li></ul>

<p>The commands are initially sorted by weight (using commands.sort()). This displays the most popular (highest weight) commands at the top of the menu. Displaying the <a href="https://github.com/benwinding/command-pal/pull/30#issuecomment-1418245061" rel="nofollow">search results sorted by weight</a> is an ongoing project.</p>

<p>The command is shown if its <code>contexts</code> property matches the current context. For example, the task timing commands only make sense when editing an issue. They should not be shown when viewing a list of issues, or a user&#39;s profile page. Inspecting the page&#39;s URL determines the current context. The code filters the command list, eliminating commands that are not appropriate for the context. Only then is CommandPal invoked.</p>

<p><details>
<summary> <strong>Originally written as a 2 part series. To see the transition click here or just continue reading...</strong>
</summary>
I hope you have enjoyed learning about command palettes and command-pal in particular. In part 2, we will use command-pal to control the task timer and look at how it integrates with the tracker built using the Roundup Issue Tracker.
{% embed <a href="https://www.roundup-tracker.org" rel="nofollow">https://www.roundup-tracker.org</a> %}</p>



<p>I am a developer for the open source <a href="https://www.roundup-tracker.org" rel="nofollow">Roundup Issue Tracker</a>. It has many use cases. One is to develop issue trackers like GitHub Issues, Bugzilla, or Request Tracker. I also develop a custom issue tracker for a help desk environment. This article continues with the steps to add a task-timing feature for that tracker.</p>

<p>In <a href="https://dev.to/rouilj/implementing-a-command-palette-and-task-timer-part-1-12j7" rel="nofollow">part 1 of this series</a> I had just finished installing command-pal. Let&#39;s take a closer look at command-pal before we get to the timer.
</details></p>

<h2 id="enhancing-command-pal-and-handling-a-showstopper" id="enhancing-command-pal-and-handling-a-showstopper">Enhancing command-pal and Handling a Showstopper</h2>

<p>The  <a href="https://blog.superhuman.com/how-to-build-a-remarkable-command-palette/" rel="nofollow">Superhuman blog post</a>  lists other desirable features for a command palette:</p>
<ul><li><strong>fuzzy search</strong> (for mispelings 8-)) – is included in command-pal using <a href="https://fusejs.io/" rel="nofollow">fuse.js</a>. (It looks like there is a <a href="https://www.npmjs.com/package/@deepdub/ninja-keys" rel="nofollow">fork of Ninja Keys that has fuzzy search support</a>.)</li>
<li><strong>icons</strong> – I created an <a href="https://github.com/benwinding/command-pal/issues/22" rel="nofollow">issue</a> and <a href="https://github.com/benwinding/command-pal/pull/23" rel="nofollow">pull request</a> to add support</li>
<li><strong>synonyms</strong> – the fuzzy search includes the description field. This helps broaden the matching terms. But a description shouldn&#39;t be a keyword/synonym list. fuse.js can search an array of strings that are part of an object. Adding this functionality is a <a href="https://github.com/benwinding/command-pal/issues/8" rel="nofollow">work in progress</a>.</li></ul>

<p>One interesting possibility is supporting multiple command palettes on a page. Each palette would have a different set of commands.  I am not sure that&#39;s a good idea. Superhuman suggests making the command palette omnipotent. Multiple palettes force the user to make a decision about which palette to activate. This breaks the idea of <a href="https://www.interaction-design.org/literature/article/don-t-make-me-think-key-learning-points-for-ux-design-for-the-web" rel="nofollow">“don&#39;t make me think”</a>.  I was able to create and activate multiple palettes with different hotkeys. However, more work on <a href="https://github.com/benwinding/command-pal/issues/24" rel="nofollow">supporting multiple palettes on a page</a> is needed.</p>

<p>At its core, a command palette is a large select modal. Having the ability to <a href="https://github.com/benwinding/command-pal/pull/28" rel="nofollow">activate the modal from Javascript</a> could allow the palette to be used in more places without making the user think.</p>

<p>I am pleased with command-pal. I have found it quite hackable even though I have never used Svelte before. However, I did have one potential showstopper.</p>

<p>The tracker uses a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP" rel="nofollow">Content Security Policy (CSP)</a>. Style blocks in the page include a <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce" rel="nofollow">nonce</a>. If the nonce is missing or doesn&#39;t match the one in the CSP the style blocks are ignored. Svelte generates style blocks for each element that it creates/injects. These client-side blocks, don&#39;t have access to the server&#39;s CSP nonce. If they did have access to the nonce, the nonce would be useless for securing the page&#39;s assets. If the style blocks were in a file that could be fetched using a stylesheet link, everything would be fine. However, <a href="https://github.com/benwinding/command-pal/issues/13#issuecomment-1416721301" rel="nofollow">efforts to do this</a> with Svelt have failed. Another alternative is to generate a <a href="https://content-security-policy.com/hash/" rel="nofollow">secure hash (sha256, 384, or 512)</a>. How to get this generated at build time is unclear. However, <a href="https://github.com/benwinding/command-pal/issues/13" rel="nofollow">I did find a way</a> to calculate it at runtime that seems to work. I <a href="https://github.com/benwinding/command-pal/pull/21" rel="nofollow">proposed a patch to allow an administrator to generate the hashes</a> using the command-pal library.</p>

<p>The mechanism for controlling the task timer is done. Now to turn my attention to actually timing tasks.</p>

<h2 id="let-s-time-all-the-things" id="let-s-time-all-the-things">Let&#39;s Time All The Things</h2>

<p>I chose the <a href="http://albert-gonzalez.github.io/easytimer.js/" rel="nofollow">easytimer.js</a> library. It supports:</p>
<ul><li>setting an initial value to start counting time</li>
<li>one minute timer granularity – to reduce CPU load</li>
<li>pausing and restarting timers while keeping their accumulated time.</li></ul>

<p> The command palette allows the gross controls: stop/start/pause/resume. I still need to handle the other parts of the UI. The existing issue page provides a field for manually entering the task time. Rather than trying to create a new UI for the timer, I reused the existing field. The UI is relatively simple. There is a “Time spent” input element referred to as the “time element” below.</p>
<ol><li>If the user prefills the time element with a number of minutes, the timer will start counting up from that time. This is helpful if you forgot to start the timer and start it after say 10 minutes.</li>
<li>The use case only requires 1 minute precision. Since I am counting in minutes, I add one minute to the start time. This rounds the time up to the next minute.</li>
<li>The time element is updated only once a minute. This is great for reducing CPU use, but poor for user feedback. There needs to be some way to notify the user that the timer is running. This needs to work without interfering with the ability to use the rest of the issue interface. Using a popup could work. But popups clutter the interface. If it can&#39;t be moved, it may hide something the user wants to use.
Instead, I cycle the background color for the time element from yellow to goldenrod every 5 seconds. This is done using CSS rather than javascript. It should perform better than updating the input with a flashing indicator every second.</li>
<li>The animation stops when the timer is paused. But the yellow background color is still shown in the time element.</li>
<li>When the timer stops, the background of the time element returns to white.</li>
<li>Besides the time element displaying state, other elements of the page change as well. Starting the timer makes a pending change to the issue. The issue page already has a mechanism for indicating a page with a pending change. Starting the timer triggers this mechanism. This results in:
<ul><li>a change in the background color of the time element label (“Time spent”)</li>
<li>a change in the background color for the H1 header on the page</li>
<li>the H1 header on the page gets “pending changes” appended</li>
<li>the title for the page has an exclamation mark prepended to it; allowing the page to be identified in a list of page titles</li>
<li>the favicon for the page is overlayed with an exclamation mark inside a yellow dot; allowing the tab to be identified if the text can&#39;t be shown.</li></ul></li></ol>

<h3 id="the-command-pal-javascript-entries" id="the-command-pal-javascript-entries">The command-pal Javascript Entries</h3>

<p>Here are the three commands for working with the timer:</p>

<pre><code class="language-js">// more commands
{
  name: &#34;Start or Unpause Timer&#34;,
  description: &#34;timer&#34;,
  contexts: [ &#34;issue.item&#34; ],  // only show timers on an issue page
  handler: () =&gt; {
    if ( ! window.userTimer ) {
      try {
	// create the object
	window.userTimer = {
	  timer: new easytimer.Timer({precision: &#34;minutes&#34;}),
	  timeField: document.getElementById(&#34;time&#34;),
	  animation: null,
	}
      } catch (err) {
	alert(`Error: ${err.name} - ${err.message}`)
      }
    }

    let timer = window.userTimer.timer;
    let timeField = null;
    if ( window.userTimer.timeField ) {
      timeField = window.userTimer.timeField;
    } else {
      alert(&#34;Unable to find &#39;Time Spent&#39; field. Are you viewing an issue?&#34;);
      return;
    }

    if ( timer.isRunning() ) {
      alert(&#34;Timer is running for: &#34; + 
	    timer.getTimeValues())
      return;
    }

    // restart timer
    if ( timer.isPaused() ) {
      timer.start();
      alert(&#34;Timer restarted at: &#34; + 
	    timer.getTimeValues())
      window.userTimer.animation.play()
      return;
    }

    // start timer instance
    let timeValue = parseInt(timeField.value);
    if (! timeValue || isNaN(timeValue)) { timeValue=0; }
    // round up time to next minute: 10 seconds -&gt; 1 minute
    let startValues = { minutes: timeValue + 1 }

    timer.start({
      startValues: startValues,
      callback: function (timer) {
	let timeField = window.userTimer.timeField
	timeField.value = timer.getTotalTimeValues().minutes;
      }
    });
    alert(&#34;Timer started at: &#34; +
	  timer.getTimeValues())
    window.userTimer.animation = animate_timer(timeField);

    timeField.value = timer.getTotalTimeValues().minutes;

    // mark field/page with a pending change
    timeField.dispatchEvent(new Event(&#39;change&#39;,
				      {bubbles: true}))
  },
  icon: &#39;&lt;span class=&#34;icon&#34;&gt;&amp;nbsp;⏱️ &lt;/span&gt;&#39;, // stopwatch emoji
  weight: 10, // sort this to the top of the list
},
{
  name: &#34;Pause Timer&#34;,
  contexts: [ &#34;issue.item&#34; ],
  description: &#34;Update time field and snooze the timer&#34;,
  handler: () =&gt; {
    if (! (window.userTimer &amp;&amp; window.userTimer.timer) ) {
      alert(&#34;No timer was started.&#34;);
      return;
    }

    let timer = window.userTimer.timer
    let timeField = window.userTimer.timeField
    let animation = window.userTimer.animation

    if ( ! timer.isRunning() ) {
      alert(&#34;Timer is not running, time is: &#34; +
	    timer.getTimeValues());
      return;
    }
    timer.pause();
    animation.pause()

    timeField.value = timer.getTotalTimeValues().minutes;
    alert(&#34;Timer paused at: &#34; +  timer.getTimeValues());
  },
},
{
  name: &#34;Stop Timer&#34;,
  contexts: [ &#34;issue.item&#34; ],
  handler: () =&gt; {
    if (! (window.userTimer &amp;&amp; window.userTimer.timer) ) {
      alert(&#34;No timer was started.&#34;);
      return;
    }

    let timer = window.userTimer.timer

    if ( ! ( timer.isRunning() || timer.isPaused() )) {
      alert(&#34;Timer is not running, time is: &#34; +
	    timer.getTimeValues());
      return;
    }

    let animation = window.userTimer.animation
    let timeField = window.userTimer.timeField

    timer.pause(); // stop time update
    timeField.value = timer.getTotalTimeValues().minutes;
    timer.stop(); // also zero&#39;s timer.
    animation.cancel();
    window.userTimer.animation = null;
  },
},
// more commands
</code></pre>

<p>(Note: the version of command-pal that I am running has the change to support icons.)</p>

<p>There is one helper function to set up the animation for the “Time spent” input field:</p>

<pre><code class="language-js">function animate_timer(input) {
  return input.animate(
    [
      {backgroundColor: &#39;yellow&#39;, easing: &#39;linear&#39;},
      {backgroundColor: &#39;goldenrod&#39;, easing: &#39;linear&#39;},
      {backgroundColor: &#39;yellow&#39;, easing: &#39;linear&#39;}
    ],
    {duration: 5000, iterations: Infinity}
  );
}
</code></pre>

<h2 id="working-example" id="working-example">Working Example</h2>

<p>You can see this in action on the <a href="https://rouilj.dynamic-dns.net/dev_to_demo/issue34" rel="nofollow">demo site</a>. Use demo/demo for login. Activate the palette using <code>ctrl+space</code>. The <a href="https://rouilj.dynamic-dns.net/fossil/roundup_sysadmin/timeline?r=command_palette_and_timer&amp;c=2023-02-02+17%3A34%3A05" rel="nofollow">code is also published</a>.</p>

<p><a href="/rouilj/tag:javascript" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">javascript</span></a> <a href="/rouilj/tag:opensource" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">opensource</span></a> <a href="/rouilj/tag:DevDotTo" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">DevDotTo</span></a> <a href="/rouilj/tag:WebDev" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">WebDev</span></a></p>
]]></content:encoded>
      <guid>https://blog.rouilj.dynamic-dns.net/rouilj/implementing-a-command-palette-and-task-timer</guid>
      <pubDate>Sun, 19 Feb 2023 22:35:18 +0000</pubDate>
    </item>
    <item>
      <title>A Modern Form Update and Interior Border Hack with CSS Grid </title>
      <link>https://blog.rouilj.dynamic-dns.net/rouilj/i-am-a-developer-on-the-roundup-issue-tracker</link>
      <description>&lt;![CDATA[&#xA;I am a developer on the Roundup Issue Tracker. Roundup is a trouble ticketing system written in Python. It is very flexible. Developers, researchers, and support staff use Roundup to define and manage the lifecycle of issues.&#xA;&#xA;My background is in system administration. So I have built a tracker template designed for tracking system administration issues. Recently I redesigned the look of one of the more complex forms.&#xA;&#xA;This is the (short) story of the decisions I made and the techniques I used.&#xA;&#xA;!--more--&#xA;&#xA;The starting point&#xA;&#xA;I have limited design experience. When I set this up many years ago, it looked ok to me.&#xA;&#xA;A web form showing multiple fields (title, priority, status, etc.). Some span the width of the page, some are inside one of two columns. Fieldsets group the fields. Collapsed fieldsets show only their headers. The form has bold and bright-colored section headers. The expanded fieldsets have grooved borders.&#xA;&#xA;Looking at it today, the bold color swatches behind the fieldset legends and grooved borders make it seem cluttered and busy. It needs a more modern look.&#xA;&#xA;Step 1 - fewer borders&#xA;&#xA;First I removed the borders from the fieldsets. This brought up an issue. Tabbing through the form moves you down the columns (divs) and not across the rows. The borders and the gap between them helped re-enforce this.&#xA;&#xA;I decided to add a vertical rule between the columns. I set border-inline-end  to 2 pixels (2px) on the first column. When the first column was longer than the second this looked great. But, the fieldset panes in the first column are collapsible. When the first column was shorter than the second column, the line between the columns didn&#39;t reach the bottom of the second column.&#xA;&#xA;The solution I came up with was to add a 2px border-inline-start on the second column. Then I moved the second column back by 2px (the width of the line) using margin-inline-start. These overlapped border lines always span the entire height of the longest column.&#xA;&#xA;The layout uses the Gridiculous grid system rather than display: grid. This requires one last tweak. On narrower screens (e.g. a cell phone), the two side-by-side columns stack. Adding an @media query to remove the borders when on a narrow display completed the tweak.&#xA;&#xA;How to make a vertical rule with css grid&#xA;&#xA;I plan to use display: grid at some point for this layout. It would reduce the amount of CSS I need to serve and provide a more flexible layout. But, there is no mechanism for creating a rule in the gaps of a grid layout. I found a trick for doing that, but I haven&#39;t committed to using it yet. There is one nice advantage to using this trick. When the columns stack, grid removes the gap and also the (pseudo) vertical rule. No need for an @media cleanup.&#xA;&#xA;To create the rule, I created a div (the base div) with the background-color set to the vertical rule&#39;s color. I set the CSS for that div to  display: grid and the gap: 0 2px. I placed two divs inside the base div. They form the columns. Set their background-color to that of the rest of the page. Now the 2px column gap in the grid exposes the darker rule color of the base div. You can only emulate a solid border, but it works for my use case. Could linear gradients emulate a grooved, ridged, dotted, or dashed border?&#xA;&#xA;My eyes, my eyes&#xA;&#xA;The second change was to remove the banners of color behind the fieldset legends. With the more colorful themes, it&#39;s a bit bright on the eyes 8-). A simple underline matching the monochromatic theme replaces it. The HTML for the legend is legendspanTitle Goes Here/span/legend.  Removing the background color from legend   span cleared the banner. Since the span is set to use display: block, adding a 2px border-block-end completed the work. &#xA;&#xA;The final look&#xA;&#xA;The final redesign with  a form like the one above except with the changes described in the article.&#xA;&#xA;This is a significant improvement.&#xA;&#xA;But it&#39;s still (a) bad form...&#xA;&#xA;One question that you may ask is why tab order moves down the first/left column rather than across the form. As forms go this breaks a few rules:&#xA;&#xA;  use a single column for forms. In this case, I have&#xA;     not only two primary columns, but the basic &#xA;     fieldset has 2 subcolumns.&#xA;  put labels above their control, or right-aligned&#xA;     labels to the left of the control (for ltr&#xA;     languages). Here, I use left-aligned labels to the left&#xA;     of the controls.&#xA;&#xA;To justify my decisions, let&#39;s look at the use cases. As with most trackers, there are two types of users of this tracker. The:&#xA;&#xA;  people who need something done, and&#xA;  the people doing the work.&#xA;&#xA;There is also a third user, the person modifying the tracker for use at their site.&#xA;&#xA;The left-hand column provides information for the people who need something done. Tabbing down the column moves the page like there was only one column. Ideally, these users would scan down the left column of the page. Using left-aligned labels provides this scan line.&#xA;&#xA;Since Roundup can look at the user&#39;s role and remove/hide/collapse the second column we can get even closer to rule 1. This would make it a simpler form for these users. Still, displaying key status info above the fold requires packing a lot of fields in a small area. Following the &#34;one column&#34;, &#34;label on top&#34; guidelines would increase scrolling.&#xA;&#xA;There is one more tweak for this redesign. With the second column displayed, a skip link  will be added at the bottom of the first column. The skip link will jump to the update textarea where the user can add a change note/update. This bypasses all the tab stops in the second column.&#xA;&#xA;Adding a skip link at the top of the page to target the update  textarea area is an idea. However, a skip link already exists at the top of the page. It jumps to the main section, bypassing the left-hand navigation column (not shown in the images). Would adding a second top-of-page skip link be confusing clutter? I&#39;m not sure.&#xA;&#xA;For people doing the work, references to the schedule and linked tickets are more useful. The second column provides access to them. The &#34;All Fields&#34; tab provides even more information hidden from view. For example:&#xA;&#xA;   summaries of related tickets&#xA;   a graph of relationships among tickets&#xA;   extra fields for scheduling automatic actions.&#xA;&#xA;This brings us to the third user. I tried to set up a form that the Roundup admin could adapt for their workflows. The admin should not have to be an HTML/CSS expert. The idea of Roundup is that it is customizable for many use cases. I have tried to use clean semantic HTML and simple CSS to support that idea.&#xA;&#xA;Thanks for reading this. I hope you enjoyed it and learned something. You can find more information on Roundup using the link below.&#xA;{% embed https://www.roundup-tracker.org?ref=dev.to-modernformupdate %}&#xA;&#xA;#RoundupTracker #DevDotTo #WebDev]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s77umppmq50ebupd7xih.png" alt="" title="Banner image.">
I am a developer on the <a href="https://www.roundup-tracker.org?ref=local-modern_form_update" rel="nofollow">Roundup Issue Tracker</a>. Roundup is a trouble ticketing system written in Python. It is very flexible. Developers, researchers, and support staff use Roundup to define and manage the lifecycle of issues.</p>

<p>My background is in system administration. So I have built a tracker template designed for tracking system administration issues. Recently I redesigned the look of one of the more complex forms.</p>

<p>This is the (short) story of the decisions I made and the techniques I used.</p>



<h2 id="the-starting-point" id="the-starting-point">The starting point</h2>

<p>I have limited design experience. When I set this up many years ago, it looked ok to me.</p>

<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s77umppmq50ebupd7xih.png" alt="A web form showing multiple fields (title, priority, status, etc.). Some span the width of the page, some are inside one of two columns. Fieldsets group the fields. Collapsed fieldsets show only their headers. The form has bold and bright-colored section headers. The expanded fieldsets have grooved borders." title="Form before changes."></p>

<p>Looking at it today, the bold color swatches behind the fieldset legends and grooved borders make it seem cluttered and busy. It needs a more modern look.</p>

<h2 id="step-1-fewer-borders" id="step-1-fewer-borders">Step 1 – fewer borders</h2>

<p>First I removed the borders from the fieldsets. This brought up an issue. Tabbing through the form moves you down the columns (divs) and not across the rows. The borders and the gap between them helped re-enforce this.</p>

<p>I decided to add a vertical rule between the columns. I set <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border-inline-end" rel="nofollow">border-inline-end</a>  to 2 pixels (2px) on the first column. When the first column was longer than the second this looked great. But, the fieldset panes in the first column are collapsible. When the first column was shorter than the second column, the line between the columns didn&#39;t reach the bottom of the second column.</p>

<p>The solution I came up with was to add a 2px border-inline-start on the second column. Then I moved the second column back by 2px (the width of the line) using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/margin-inline-start" rel="nofollow">margin-inline-start</a>. These overlapped border lines always span the entire height of the longest column.</p>

<p>The layout uses the <a href="http://gridiculo.us/" rel="nofollow">Gridiculous</a> grid system rather than <code>display: grid</code>. This requires one last tweak. On narrower screens (e.g. a cell phone), the two side-by-side columns stack. Adding an @media query to remove the borders when on a narrow display completed the tweak.</p>

<h2 id="how-to-make-a-vertical-rule-with-css-grid" id="how-to-make-a-vertical-rule-with-css-grid">How to make a vertical rule with css grid</h2>

<p>I plan to use <code>display: grid</code> at some point for this layout. It would reduce the amount of CSS I need to serve and provide a more flexible layout. But, there is no mechanism for creating a rule in the gaps of a grid layout. I found a trick for doing that, but I haven&#39;t committed to using it yet. There is one nice advantage to using this trick. When the columns stack, grid removes the gap and also the (pseudo) vertical rule. No need for an @media cleanup.</p>

<p>To create the rule, I created a div (the base div) with the background-color set to the vertical rule&#39;s color. I set the CSS for that div to  <code>display: grid</code> and the <code>gap: 0 2px</code>. I placed two divs inside the base div. They form the columns. Set their background-color to that of the rest of the page. Now the 2px column gap in the grid exposes the darker rule color of the base div. You can only emulate a solid border, but it works for my use case. Could linear gradients emulate a grooved, ridged, dotted, or dashed border?</p>

<h2 id="my-eyes-my-eyes" id="my-eyes-my-eyes">My eyes, my eyes</h2>

<p>The second change was to remove the banners of color behind the fieldset legends. With the more colorful themes, it&#39;s a bit bright on the eyes 8-). A simple underline matching the monochromatic theme replaces it. The HTML for the legend is <code>&lt;legend&gt;&lt;span&gt;Title Goes Here&lt;/span&gt;&lt;/legend&gt;</code>.  Removing the background color from <code>legend &gt; span</code> cleared the banner. Since the span is set to use <code>display: block</code>, adding a 2px <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border-block-end" rel="nofollow">border-block-end</a> completed the work.</p>

<h2 id="the-final-look" id="the-final-look">The final look</h2>

<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ymsg4q8cxnvs06eljw6s.png" alt="The final redesign with  a form like the one above except with the changes described in the article." title="Form after the changes in this article."></p>

<p>This is a significant improvement.</p>

<h2 id="but-it-s-still-a-bad-form" id="but-it-s-still-a-bad-form">But it&#39;s still (a) bad form...</h2>

<p>One question that you may ask is why tab order moves down the first/left column rather than across the form. As forms go this breaks a few rules:</p>
<ol><li>use a single column for forms. In this case, I have
 not only two primary columns, but the basic
 fieldset has 2 subcolumns.</li>
<li>put labels above their control, or right-aligned
 labels to the left of the control (for ltr
 languages). Here, I use left-aligned labels to the left
 of the controls.</li></ol>

<p>To justify my decisions, let&#39;s look at the use cases. As with most trackers, there are two types of users of this tracker. The:</p>
<ol><li>people who need something done, and</li>
<li>the people doing the work.</li></ol>

<p>There is also a third user, the person modifying the tracker for use at their site.</p>

<p>The left-hand column provides information for the people who need something done. Tabbing down the column moves the page like there was only one column. Ideally, these users would scan down the left column of the page. Using left-aligned labels provides this scan line.</p>

<p>Since Roundup can look at the user&#39;s role and remove/hide/collapse the second column we can get even closer to rule 1. This would make it a simpler form for these users. Still, displaying key status info above the fold requires packing a lot of fields in a small area. Following the “one column”, “label on top” guidelines would increase scrolling.</p>

<p>There is one more tweak for this redesign. With the second column displayed, a <a href="https://webaim.org/techniques/skipnav/" rel="nofollow">skip link</a>  will be added at the bottom of the first column. The skip link will jump to the update textarea where the user can add a change note/update. This bypasses all the tab stops in the second column.</p>

<p>Adding a skip link at the top of the page to target the update  textarea area is an idea. However, a skip link already exists at the top of the page. It jumps to the main section, bypassing the left-hand navigation column (not shown in the images). Would adding a second top-of-page skip link be confusing clutter? I&#39;m not sure.</p>

<p>For people doing the work, references to the schedule and linked tickets are more useful. The second column provides access to them. The “All Fields” tab provides even more information hidden from view. For example:</p>
<ol><li>summaries of related tickets</li>
<li>a graph of relationships among tickets</li>
<li>extra fields for scheduling automatic actions.</li></ol>

<p>This brings us to the third user. I tried to set up a form that the Roundup admin could adapt for their workflows. The admin should not have to be an HTML/CSS expert. The idea of Roundup is that it is customizable for many use cases. I have tried to use clean semantic HTML and simple CSS to support that idea.</p>

<p>Thanks for reading this. I hope you enjoyed it and learned something. You can find more information on Roundup using the link below.
{% embed <a href="https://www.roundup-tracker.org?ref=dev.to-modern_form_update" rel="nofollow">https://www.roundup-tracker.org?ref=dev.to-modern_form_update</a> %}</p>

<p><a href="/rouilj/tag:RoundupTracker" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">RoundupTracker</span></a> <a href="/rouilj/tag:DevDotTo" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">DevDotTo</span></a> <a href="/rouilj/tag:WebDev" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">WebDev</span></a></p>
]]></content:encoded>
      <guid>https://blog.rouilj.dynamic-dns.net/rouilj/i-am-a-developer-on-the-roundup-issue-tracker</guid>
      <pubDate>Mon, 28 Nov 2022 17:01:41 +0000</pubDate>
    </item>
    <item>
      <title>My Journey to Open-Source Development</title>
      <link>https://blog.rouilj.dynamic-dns.net/rouilj/my-journey-to-open-source-development</link>
      <description>&lt;![CDATA[&lt;!--&#xA;comment: front matter from dev.to&#xA;published: true&#xA;description: How I started as a developer of the Roundup Issue Tracker.&#xA;canonicalurl: &#xA;coverimage: https://www.roundup-tracker.org/images/indexloggedout.png?ref=dev.to1&#xA;tags: #python, #webdev, #opensource &#xA;series: Roundup Tracker - notes from an open-source developer&#xA;--  Welcome to the first article on the ongoing development of the Roundup Issue Tracker. Before we get started, a little about me.&#xA;&#xA;I have a B.S. in Physics which included some advanced computer science courses. But, my career ended up in system administration. I have taught and published a few peer-reviewed papers at conferences over the years. Trying to reduce toil and improve operations, lead to my interest in ticketing systems. Tools from LEAN and Six Sigma have proven valuable in analyzing ticketing and real-time logging data.&#xA;&#xA;Issues Need Tracking&#xA;In 2002, I ran a lab that installed a system monitoring solution on Sun computers. We needed to track requests that came to the lab. I evaluated ticketing systems across a few dimensions. The Roundup Issue Tracker scored the highest.&#xA;&#xA;Five minutes after downloading the software, I had a classic tracker running in demo mode. After entering a few tickets and adding a few keywords (tags), my co-worker and I decided it could work. It was flexible and looked like we could extend it to support our future growth. Little did I know that this was the start of a 20+ year relationship.&#xA;!--more--&#xA;Roundup Basics&#xA;Roundup tracker has two components: the Roundup core and the tracker instance. The core is written in Python. The tracker instance supports one of two HTML templating engines: (Zope Page Templates or Jinja2). It is programmed in Python.  The tracker instance controls the look/feel/functionality of the tracker. It:&#xA;&#xA;   defines the database schema/data model,&#xA;   provides the web interface (with customizable HTML and CSS),&#xA;   executes Python code to validate entered data and react to a change (e.g. by emailing notification messages),&#xA;   can hook into the core code to control how the core operates.&#xA;&#xA;One instance of the Roundup core can run many trackers. Each tracker can have a very different presentation and function.&#xA;&#xA;Getting My Feet Wet&#xA;When I started, Roundup was still very new. I started developing my customized tracker to learn Python and experiment with Roundup. My tracker implemented several ideas from Request Tracker (RT). I developed the key components, replies/comments, dependencies, and grouping (parent/child), in less than 40 hours of effort over a few weeks. Roundup&#39;s implementation and documentation made building these significant features easy.&#xA;&#xA;The other advantage was that Richard Jones, the developer, was in Australia. We set up a &#34;follow the sun&#34; development cycle. I emailed him a problem when I went to bed. By the time I got up the next morning, he would have a fix ready for me to use when I got home after work. This fast feedback is something I try to maintain in the Roundup community.&#xA;&#xA;I never deployed my custom tracker, but the classic tracker worked well for the lab. As a result, the sales engineering group requested (and got) a tracker. After I left the company in 2005, they upgraded the Roundup core and converted the database to MySQL (from a dbm key/value store).&#xA;&#xA;Back in a More Active Role&#xA;My next IT job used Request Tracker. I followed Roundup development but didn&#39;t contribute much. In 2012, there was some interest at work in setting up a customer-facing portal based on Roundup. I started development on my tracker again and re-joined the Roundup community. Over the next few years, I developed code to add:&#xA;&#xA;  a transaction source property for data changes. Makes changes from the web interface but prevents the same change if it originated from (unsigned) email.&#xA;  the filter command to the roundup-admin command line interface makes data searching easier.&#xA;  nofollow relationships to links which makes Roundup less useful to spammers.&#xA;  CSRF protection using synchronizer tokens makes Roundup more secure.&#xA;&#xA;Releasing Roundup&#xA;By 2018, Richard had stepped back, the last release was January 2016. I took over as a lead developer and release engineer. I released version 1.6.0 in July of 2018. This merged several existing patches, cleaning up Roundup&#39;s issue tracker.&#xA;&#xA;Since 2018, there were two major changes added by other developers:&#xA;&#xA;   support for both Python 2 and Python 3 (which required a 2.0.0 release)&#xA;   addition of a REST interface beside the XML-RPC interface&#xA;&#xA;On July 13th, 2022, I made my fifth annual release.&#xA;&#xA;Since Roundup deployments are on-prem, I established a yearly release cadence. Other issue trackers have more frequent releases. But, many of those releases address UI or workflow issues. In Roundup these are implemented in the (customized) tracker instance. So the user is not dependent on the fixes pushed from upstream. Each release includes upgrading instructions. These instructions can include changes for the tracker instance to bring it up to date.&#xA;&#xA;The slower release cadence has an advantage for the users. It reduces the need to update tracker instances to once a year rather than every few months. However, the yearly cadence results in less public visibility for Roundup.  &#xA;&#xA;I plan more articles on developing and maintaining an open-source application. Some future topics I am considering include (smallin no particular order/small):&#xA;&#xA;   The Customisability Trap&#xA;   Kitchen Sink Development&#xA;   Accessibility&#xA;   Responsive Design&#xA;   The Problem with Selects&#xA;   What?? No Javascript!&#xA;   Docker All the Things&#xA;   Colors and Themes&#xA;   The Bus Factor&#xA;   Evaluating Tracking Software&#xA;&#xA;The Roundup 20supth/sup anniversary article on lwn.net includes more information on Roundup.&#xA;&#xA;I hope you enjoyed this article. Thanks for reading.&#xA;&#xA;tags: #Python, #WebDev, #opensource #RoundupTracker #seriesRoundupTrackerNotesFromAnOpenSourceDeveloper&#xA;DevDotTo&#xA;]]&gt;</description>
      <content:encoded><![CDATA[

<p>Welcome to the first article on the ongoing development of the <a href="https://www.roundup-tracker.org?ref=dev.to_1" rel="nofollow">Roundup Issue Tracker</a>. Before we get started, a little about me.</p>

<p>I have a B.S. in Physics which included some advanced computer science courses. But, my career ended up in system administration. I have <a href="https://www.usenix.org/legacy/events/lisa09/training/tutonefile.html#s2" rel="nofollow">taught</a> and published a <a href="https://www.usenix.org/legacy/events/lisa04/tech/full_papers/rouillard/rouillard_html/index.html" rel="nofollow">few</a> peer-reviewed <a href="https://static.usenix.org/publications/library/proceedings/lisa94/rouillard.html" rel="nofollow">papers</a> at conferences over the years. Trying to reduce toil and improve operations, lead to my interest in ticketing systems. Tools from LEAN and Six Sigma have proven valuable in analyzing ticketing and real-time logging data.</p>

<h2 id="issues-need-tracking" id="issues-need-tracking">Issues Need Tracking</h2>

<p>In 2002, I ran a lab that installed a system monitoring solution on Sun computers. We needed to track requests that came to the lab. I evaluated ticketing systems across a few dimensions. The <a href="https://www.roundup-tracker.org?ref=dev.to_1" rel="nofollow">Roundup Issue Tracker</a> scored the highest.</p>

<p>Five minutes after downloading the software, I had a classic tracker running in demo mode. After entering a few tickets and adding a few keywords (tags), my co-worker and I decided it could work. It was flexible and looked like we could extend it to support our future growth. Little did I know that this was the start of a 20+ year relationship.
</p>

<h2 id="roundup-basics" id="roundup-basics">Roundup Basics</h2>

<p>Roundup tracker has two components: the Roundup core and the tracker instance. The core is written in Python. The tracker instance supports one of two HTML templating engines: (<a href="https://pagetemplates.readthedocs.io/en/latest/introduction.html" rel="nofollow">Zope Page Templates</a> or <a href="https://palletsprojects.com/p/jinja/" rel="nofollow">Jinja2</a>). It is programmed in Python.  The tracker instance controls the look/feel/functionality of the tracker. It:</p>
<ul><li>defines the database schema/data model,</li>
<li>provides the web interface (with customizable HTML and CSS),</li>
<li>executes Python code to <a href="https://www.roundup-tracker.org/docs/customizing.html?ref=dev.to_1#detectors-adding-behaviour-to-your-tracker" rel="nofollow">validate entered data and react</a> to a change (e.g. by emailing notification messages),</li>
<li>can <a href="https://www.roundup-tracker.org/docs/customizing.html?ref=dev.to_1#interfaces-py-hooking-into-the-core-of-roundup" rel="nofollow">hook into the core code</a> to control how the core operates.</li></ul>

<p>One instance of the Roundup core can run many trackers. Each tracker can have a very different presentation and function.</p>

<h2 id="getting-my-feet-wet" id="getting-my-feet-wet">Getting My Feet Wet</h2>

<p>When I started, Roundup was still very new. I started developing my customized tracker to learn Python and experiment with Roundup. My tracker implemented several ideas from <a href="https://bestpractical.com/request-tracker/" rel="nofollow">Request Tracker (RT)</a>. I developed the key components, <a href="https://rt-wiki.bestpractical.com/wiki/ManualUsingWebInterface#Replying_and_commenting" rel="nofollow">replies/comments</a>, <a href="https://rt-wiki.bestpractical.com/wiki/ManualUsingWebInterface#Links" rel="nofollow">dependencies, and grouping (parent/child)</a>, in less than 40 hours of effort over a few weeks. Roundup&#39;s implementation and documentation made building these significant features easy.</p>

<p>The other advantage was that Richard Jones, the developer, was in Australia. We set up a “follow the sun” development cycle. I emailed him a problem when I went to bed. By the time I got up the next morning, he would have a fix ready for me to use when I got home after work. This fast feedback is something I try to maintain in the Roundup community.</p>

<p>I never deployed my custom tracker, but the classic tracker worked well for the lab. As a result, the sales engineering group requested (and got) a tracker. After I left the company in 2005, they upgraded the Roundup core and converted the database to MySQL (from a dbm key/value store).</p>

<h2 id="back-in-a-more-active-role" id="back-in-a-more-active-role">Back in a More Active Role</h2>

<p>My next IT job used Request Tracker. I followed Roundup development but didn&#39;t contribute much. In 2012, there was some interest at work in setting up a customer-facing portal based on Roundup. I started development on my tracker again and re-joined the Roundup community. Over the next few years, I developed code to add:</p>
<ul><li>a transaction source property for data changes. Makes changes from the web interface but prevents the same change if it originated from (unsigned) email.</li>
<li>the filter command to the <code>roundup-admin</code> command line interface makes data searching easier.</li>
<li><code>nofollow</code> relationships to links which makes Roundup less useful to spammers.</li>
<li><a href="https://owasp.org/www-community/attacks/csrf" rel="nofollow">CSRF</a> protection using <a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern" rel="nofollow">synchronizer tokens</a> makes Roundup more secure.</li></ul>

<h2 id="releasing-roundup" id="releasing-roundup">Releasing Roundup</h2>

<p>By 2018, Richard had stepped back, the last release was January 2016. I took over as a lead developer and release engineer. I released version 1.6.0 in July of 2018. This merged several existing patches, cleaning up <a href="https://issues.roundup-tracker.org?ref=dev.to_1" rel="nofollow">Roundup&#39;s issue tracker</a>.</p>

<p>Since 2018, there were two major changes added by other developers:</p>
<ul><li>support for both Python 2 and Python 3 (which required a 2.0.0 release)</li>
<li>addition of a <a href="https://www.roundup-tracker.org/docs/rest.html?ref=dev.to_1" rel="nofollow">REST interface</a> beside the <a href="https://www.roundup-tracker.org/docs/xmlrpc.html?ref=dev.to_1" rel="nofollow">XML-RPC</a> interface</li></ul>

<p>On July 13th, 2022, I made my <a href="https://sourceforge.net/p/roundup/mailman/roundup-users/thread/20210713043457.083CE6A0022%40pe15.cs.umb.edu/" rel="nofollow">fifth annual release</a>.</p>

<p>Since Roundup deployments are on-prem, I established a yearly release cadence. Other issue trackers have more frequent releases. But, many of those releases address UI or workflow issues. In Roundup these are implemented in the (customized) tracker instance. So the user is not dependent on the fixes pushed from upstream. Each release includes <a href="https://www.roundup-tracker.org/docs/upgrading.html?ref=dev.to_1" rel="nofollow">upgrading instructions</a>. These instructions can include changes for the tracker instance to bring it up to date.</p>

<p>The slower release cadence has an advantage for the users. It reduces the need to update tracker instances to once a year rather than every few months. However, the yearly cadence results in less public visibility for Roundup.</p>

<p>I plan more articles on developing and maintaining an open-source application. Some future topics I am considering include (<small>in no particular order</small>):</p>
<ul><li>The Customisability Trap</li>
<li>Kitchen Sink Development</li>
<li>Accessibility</li>
<li>Responsive Design</li>
<li>The Problem with Selects</li>
<li>What?? No Javascript!</li>
<li>Docker All the Things</li>
<li>Colors and Themes</li>
<li>The Bus Factor</li>
<li>Evaluating Tracking Software</li></ul>

<p>The <a href="https://lwn.net/Articles/869118/" rel="nofollow">Roundup 20<sup>th</sup> anniversary article on lwn.net</a> includes more information on Roundup.</p>

<p>I hope you enjoyed this article. Thanks for reading.</p>

<p>tags: <a href="/rouilj/tag:Python" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">Python</span></a>, <a href="/rouilj/tag:WebDev" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">WebDev</span></a>, <a href="/rouilj/tag:opensource" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">opensource</span></a> <a href="/rouilj/tag:RoundupTracker" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">RoundupTracker</span></a> <a href="/rouilj/tag:seriesRoundupTrackerNotesFromAnOpenSourceDeveloper" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">seriesRoundupTrackerNotesFromAnOpenSourceDeveloper</span></a>
<a href="/rouilj/tag:DevDotTo" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">DevDotTo</span></a></p>
]]></content:encoded>
      <guid>https://blog.rouilj.dynamic-dns.net/rouilj/my-journey-to-open-source-development</guid>
      <pubDate>Fri, 29 Jul 2022 05:49:19 +0000</pubDate>
    </item>
  </channel>
</rss>