<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Ray Berger's Blog]]></title><description><![CDATA[Ray Berger's Blog]]></description><link>https://blog.rayberger.org</link><generator>RSS for Node</generator><lastBuildDate>Sun, 19 Apr 2026 02:21:31 GMT</lastBuildDate><atom:link href="https://blog.rayberger.org/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Caring for My Father Online]]></title><description><![CDATA[When dad’s phone died I was the first one mom called for help. She always apologizes when calling for help. I’m not sure if she’s just being polite or she really doesn’t know the joy it brings me to b]]></description><link>https://blog.rayberger.org/caring-for-my-father-online</link><guid isPermaLink="true">https://blog.rayberger.org/caring-for-my-father-online</guid><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Sat, 28 Mar 2026 00:15:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/5fcc7cd5ef5f84019de79930/c7ebb111-d1e5-4af3-b881-149877862116.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When dad’s phone died I was the first one mom called for help. She always apologizes when calling for help. I’m not sure if she’s just being polite or she really doesn’t know the joy it brings me to be the one she can count on for this small part of life. Well… I can be a little short on patience when giving technical support so I see how it’d be confusing.</p>
<p>I had a phone on hand to send them. To save us all a lot of headaches I started setting up the phone for him before sending it. I go about with the usual suspects: Gmail, Facebook, and a tracker for the price of gold, possibly more important than the former two apps.</p>
<p>My dad has been bedridden for about a year now since the revolving door of medical care that started with a case of sepsis. He uses his phone a lot and every time I visit it’s more or less the same. I appreciate that I’m trusted with his digital life and helping him with whatever odd setting may have changed or apps disappeared.</p>
<p>Upon logging into Facebook on his new phone I found an eerie ghost town of AI generated videos, shock content, and engagement bait. A talking bowl of cereal. A lion about to pounce on someone. A vintage photo of a tough guy overlaid with the text “a real man always takes care of his family. Do you agree?”</p>
<p>A few months ago, I went through his feed and unfollowed a bunch of pages and downvoted a bunch of this type of content, which seemed to help in the moment. I’ve asked dad about it before and he doesn't want to see this stuff, but it sucks him in enough to tell the algorithm to show him more.</p>
<p>Following the posts back to the groups they came from, I found quite strange content. Things like environmentalism in South Africa, but the posts were videos of people in waist-deep water picking up plastic bottles, or textile factories in Southeast Asia, or nostalgia-slop content with images of celebrities with captions like, “There’ll never be another Chuck Norris. Rest in peace.”</p>
<p>As I was "leaving" these groups, sometimes a message would pop up with a message like: “Check this box so people can’t add you to this group again.” So... someone is adding him to these groups? My dad lived a full life, but he was not traveling the world collecting strangers on Facebook. As long as he has had Facebook, I have known pretty much everyone in his life. I started rooting around his friends list and many of these accounts were definitely not people he knew. They fell into a few big categories: hot woman as the profile picture, names in Cyrillic characters, or names from Southeast Asia or Eastern Europe.</p>
<p>They accounts appeared in clusters: an account would have several mutual friends, and those mutual friends were usually connected to one another as well. It may be due to Facebook’s setting limiting friend requests to friends of friends. Some of these accounts only had five or ten friends. My theory is that after you are friends with one they tell these other accounts to add you too. Once they are connected, they start adding you to these Facebook groups, and your feed fills up with group slop. Being in the groups also seems to make it easier to pull you into spam group chats.</p>
<p>During this exercise, his friends list shrunk from ~320 to ~120.</p>
<p>Being close to my dad and knowing him as well as I do, I can make pretty good guesses about who he actually knows. If all their posts are in another language, he probably does not know them. If they say they live in some faraway country, he probably does not know them. If all they post are ads for cheap Ray-Bans or promises of free tokens for some online game, he probably does not know them. Also, none of these accounts ever had direct messages or signs of interaction with my dad.</p>
<p>I hope it means his feed will have content that’s more enriching and less outraging… but who knows. This is the labor of digital caregiving that many people are now obliged to do. Unfortunately, many more people probably have no one to do it for them.</p>
<p>It's part of why I believe people need systemic protections. My dad could have been just a few messages away from being duped into sending his life savings through a crypto ATM to someone on the other side of the world. With my urbanist friends, I often voice concern for exposing local housing markets to global finance investors. Echoing that sentiment, we should do our best to avoid exposing local aging populations to global fraud networks.</p>
<img src="https://cdn.hashnode.com/uploads/covers/5fcc7cd5ef5f84019de79930/2fc8c3e8-fd8e-4642-bbc8-e05eabb72275.png" alt="" style="display:block;margin:0 auto" />

<p>Example profile: name in Cyrillic, photos of a gambling game, posts on wall all in another language.</p>
]]></content:encoded></item><item><title><![CDATA[Mapping Thrift Stores vs. Median Household Income in San Francisco]]></title><description><![CDATA[Last year, I learned QGIS through the GIS and Spatial Analysis for Urban Practitioners course by All Things Urban. This year, with free Udemy access via SFPL, I'm looking to expand my skills. First, I dug up my old final project to revisit and share....]]></description><link>https://blog.rayberger.org/mapping-thrift-stores-vs-median-household-income-in-san-francisco</link><guid isPermaLink="true">https://blog.rayberger.org/mapping-thrift-stores-vs-median-household-income-in-san-francisco</guid><category><![CDATA[Urbanism]]></category><category><![CDATA[GIS]]></category><category><![CDATA[qgis]]></category><category><![CDATA[maps]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Thu, 22 Jan 2026 06:42:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769064049876/260824f0-766e-4e6d-b02d-67041daf0aa4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Last year, I learned QGIS through the <a target="_blank" href="https://gis.allthingsurban.net/">GIS and Spatial Analysis for Urban Practitioners</a> course by All Things Urban. This year, with free Udemy access via SFPL, I'm looking to expand my skills. First, I dug up my old final project to revisit and share.</p>
<p>I used data from OpenStreetMap for thrift stores (labeled as such, or as selling secondhand goods) and mapped it against income data in San Francisco. Somewhat surprisingly, no strong spatial patterns emerged as thrift stores are in areas of many income levels rather than clustering in high or low income areas as one might expect. Perhaps thrift stores are more centered around high foot traffic areas in the city. Beyond the analysis, I also took the opportunity to contribute back to the community by updating several outdated thrift store entries in OpenStreetMap.</p>
<h2 id="heading-final-map">Final Map</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769064080407/1e0dbbb2-b022-4c47-a4b6-d6e7634a8b60.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-data-sources">Data sources</h2>
<ul>
<li><p>Income data was from: B19013 Median Household Income in the Past 12 Months (in 2023 Inflation-Adjusted Dollars) by Census Block Group in San Francisco County from <a target="_blank" href="https://data.census.gov/table/ACSDT5Y2023.B19013?q=B19013&amp;g=050XX00US06075$1500000">here</a></p>
</li>
<li><p>2024 TIGER/Line Shapefiles: Block Groups from <a target="_blank" href="https://www.census.gov/cgi-bin/geo/shapefiles/index.php?year=2024&amp;layergroup=Block+Groups">here</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to Convert Strings to Wikidata QIDs]]></title><description><![CDATA[TLDR: I made a tool to do this: Wikidata string to QID tool (source).
Bulk adding information to Wikidata by hand is a pain. I have an idea for creating a map of MVNOs (mobile virtual network operators) and their ownership (AT&T owns Cricket, Verizon...]]></description><link>https://blog.rayberger.org/how-to-convert-strings-to-wikidata-qids</link><guid isPermaLink="true">https://blog.rayberger.org/how-to-convert-strings-to-wikidata-qids</guid><category><![CDATA[Wikipedia]]></category><category><![CDATA[Wikidata]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Wed, 29 Oct 2025 07:07:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761720350954/61b0221f-eb38-4c58-bb35-b165a3453f5d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>TLDR: I made a tool to do this: <a target="_blank" href="https://rayberger.org/onepagers/sites/wikidata-string-to-qid/">Wikidata string to QID tool</a> (<a target="_blank" href="https://github.com/RayBB/onepagers/tree/main/sites/wikidata-string-to-qid">source</a>).</p>
<p>Bulk adding information to Wikidata by hand is a pain. I have an idea for creating a map of MVNOs (mobile virtual network operators) and their ownership (AT&amp;T owns Cricket, Verizon owns Visible and many brands, T-Mobile owns Mint), like <a target="_blank" href="https://cf.foodista.com/content/fp/d5o8gpqdnaxramgo.jpg">this map of food brands</a>. I want it to be powered by Wikidata so it will update automatically and group together all the MVNOs based on who owns them and what network they run on. Since I'm focusing on the U.S., I needed to add the country field to every MVNO that didn't have it it.</p>
<p>I used Gemini (in Google Sheets) to extract the country name from the description of about 20 MVNOs that I queried from Wikidata. I wanted to add them using <a target="_blank" href="https://www.wikidata.org/wiki/Help:QuickStatements">QuickStatements</a>, a tool to bulk update Wikidata, but I couldn't get the string of the country to convert to a proper QID. I could do it manually but I wanted to get better at using Quickstatments. It may have supported functionality long ago, but doesn’t anymore, likely because there is always a chance of incorrect matches when using just the string. Georgia the state and Georgia the country are a classic example of why a bit more context is needed for accuracy.</p>
<p>Thankfully, my friend Drini showed me <a target="_blank" href="https://workspace.google.com/marketplace/app/wikipedia_and_wikidata_tools/595109124715">Wikipedia and Wikidata Tools</a>, a Google Sheets extension that lets you do this query as simply as <code>=WIKIDATAQID("en:Spain")</code>. It works decently, but giving a tool access to your account, readings the docs, and being dependent on Google aren't ideal.</p>
<p>To solve this, I created a <a target="_blank" href="https://rayberger.org/onepagers/sites/wikidata-string-to-qid/">Wikidata string to QID tool</a> (<a target="_blank" href="https://github.com/RayBB/onepagers/tree/main/sites/wikidata-string-to-qid">source</a>). Paste in a list of strings and get a list QIDs. It also shows the description so you can feel more confident in the match and go about your merry way editing Wikidata.</p>
<p>I also went ahead and updated the Wikidata QuickStatements page with some information about this, since it was not very easy to find.</p>
<p>To help people find this on the web, here are some of the ways I searched for this:</p>
<ul>
<li>How can I turn text into a Wikidata QID?</li>
<li>How to add a string as the value of a property using QuickStatements for Wikidata?</li>
<li>Resolving a string to the corresponding Wikidata ID</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Does FlixBus have free WiFi? (Yes, 250MB)]]></title><description><![CDATA[Friends often joke about how bad FixBus is but none of the ~10 rides I've been on were particularly unpleasant.
In fact, this most recent bus even had 250 MB of free WiFi. I didn’t bother using it but I found it to be an amusingly small amount for a ...]]></description><link>https://blog.rayberger.org/flixbus-wifi</link><guid isPermaLink="true">https://blog.rayberger.org/flixbus-wifi</guid><category><![CDATA[flixbus]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Tue, 14 Oct 2025 01:48:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760405819372/e00e3d0f-0ab1-495f-a8bf-ac2e0fc61651.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Friends often joke about how bad FixBus is but none of the ~10 rides I've been on were particularly unpleasant.</p>
<p>In fact, this most recent bus even had 250 MB of free WiFi. I didn’t bother using it but I found it to be an amusingly small amount for a 10 hour trip. I think it’s the first time I saw WiFi on a FlixBus. According to Reddit, they also <a target="_blank" href="https://www.reddit.com/r/czech/comments/1ke42j1/flixbus_blockuje_netflix_apod_a_youtube_hej_co%C5%BEe/">block video streaming</a>. It’s powered by <a target="_blank" href="https://www.rebelroam.com/">RebelRoam</a>, which is quite proud to provide <a target="_blank" href="https://www.rebelroam.com/solutions/network-optimization/">"Network Optimization" by blocking video.</a></p>
<p>Blocking video doesn't seem that bad for the context but it's pretty lame that they get away with advertising "free WiFi" that rarely exists and offers so little data.</p>
<p>Not sure how long they've been doing this but I found a <a target="_blank" href="https://x.com/VanishingUnder/status/1623833796305989633">Tweet</a> about it from 2023.</p>
<p>I'm not sure if it's on every route but I was traveling on bus N61 going from Amsterdam to Nuremberg on October 3rd from 10:30 pm to 8:10 am.</p>
<p>They also sold beer on the bus, another new one for me. 🍺</p>
<p>A few screenshots:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760405240194/13a0887f-5b40-4d6b-8f04-3ef35c8b95d3.png" alt /></td><td><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760405247301/2e37c36b-7bc4-4b4c-bcb2-2aea27dae25d.png" alt /></td><td><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760406256479/0ab715ca-3594-42b3-b68c-cb439ef85200.png" alt /></td></tr>
</thead>
<tbody>
<tr>
<td></td></tr>
</tbody>
</table>
</div>]]></content:encoded></item><item><title><![CDATA[Do Third-Party Launchers Work Well on Android 16? (yes)]]></title><description><![CDATA[Short answer: Yes, third-party launchers work great on Android 16. The update has resolved most of the issues I encountered with third-party launchers on Android 15 while using my Pixel 7.
Long answer:
The state of custom launchers on Android 15 was,...]]></description><link>https://blog.rayberger.org/third-party-launchers-on-android-16</link><guid isPermaLink="true">https://blog.rayberger.org/third-party-launchers-on-android-16</guid><category><![CDATA[Android]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Sat, 30 Aug 2025 00:15:59 GMT</pubDate><content:encoded><![CDATA[<p><strong>Short answer</strong>: Yes, third-party launchers work great on Android 16. The update has resolved most of the issues I encountered with third-party launchers on Android 15 while using my Pixel 7.</p>
<p><strong>Long answer</strong>:</p>
<p>The state of custom launchers on Android 15 was, to put it mildly, quite broken, particularly for Pixel phones. The problems were so widespread that <a target="_blank" href="https://www.androidpolice.com/third-party-android-launcher-developers-join-forces-voice-frustrations-to-google/">launcher developers united</a> to survey the community, hoping to pressure Google into addressing the persistent bugs. The survey's findings painted a grim picture for Pixel devices and led to a stark warning from Android Police: "<a target="_blank" href="https://www.androidpolice.com/dont-buy-a-google-pixel-want-to-use-third-party-launcher/">Don't buy a Google Pixel if you want to use a third-party launcher</a>.""</p>
<p>When Android 16 was released, <a target="_blank" href="https://www.androidauthority.com/android-16-nav-bug-fix-3568338/">Android Authority and discussions on Reddit</a> indicated that the update was buggy for some Pixel devices. I have an older Pixel 7 (we're on 10 already!) and I was concerned that Android 16 would bring more woes, so I held off on the update. While some Reddit users suggested that recent updates improved the situation, I didn't see much chatter about third-party launchers.</p>
<p>However, the third-party launcher experience on my phone seemed to be deteriorating, with frequent crashes when switching between apps. Earlier this week, I decided to take the plunge and update to Android 16.</p>
<p>While I haven't noticed many differences in general, the improvement in the custom launcher experience is a night-and-day difference. Both Smart Launcher and Nova Launcher now run almost flawlessly. App switching is smooth and no longer messes up the order of my recent apps. I am no longer seeing janky notifications, and quick switching to the previous app works as intended. Life is good.</p>
<p>When I tried searching about this on Reddit and elsewhere I didn't find any quick answers. I hope this blog post shows up and gives someone else the peace of mind to update and get a less buggy experience.</p>
<p>Also, thank you to the launcher developers who got together and pressure Google to improve the situation.</p>
]]></content:encoded></item><item><title><![CDATA[Building a FastAPI or Flask app in Coolify with UV]]></title><description><![CDATA[So, you want to start a new Python app for Coolify with the latest and greatest tools? Here’s how to do it.
At the end of the day, the process is relatively simple. However, despite having worked with all of these tools on existing projects, I hadn’t...]]></description><link>https://blog.rayberger.org/coolify-fastapi-uv</link><guid isPermaLink="true">https://blog.rayberger.org/coolify-fastapi-uv</guid><category><![CDATA[Python]]></category><category><![CDATA[coolify]]></category><category><![CDATA[self-hosted]]></category><category><![CDATA[FastAPI]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Sun, 27 Apr 2025 05:32:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1745731905795/61933213-b4d5-4686-968f-974a0b835469.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So, you want to start a new Python app for Coolify with the latest and greatest tools? Here’s how to do it.</p>
<p>At the end of the day, the process is relatively simple. However, despite having worked with all of these tools on existing projects, I hadn’t really set up new projects with them and didn’t find a complete guide so here goes.</p>
<h2 id="heading-tools-involved">Tools Involved</h2>
<ul>
<li><code>uv</code> - the replacement for pip that has been skyrocketing in popularity. It takes away a lot of the pain points.</li>
<li><code>fastapi</code> or <code>flask</code> - two popular web frameworks for Python. Flask is very barebones and time tested. fastapi is more full featured with batteries included.</li>
<li><code>coolify</code> - a very simple and easy tool for self hosting apps. Similar to Heroku or Vercel. (This tutorial assumes you already have coolify setup)</li>
<li><code>nixpacks</code> - this automatically generates docker images for you based on your repo’s structure. </li>
<li><code>GitHub</code> - hosted version control. Mentioning this here because we’ll have a push-to-deploy setup.</li>
</ul>
<h2 id="heading-steps-to-deploy-your-app">Steps to Deploy Your App</h2>
<ol>
<li><p><strong>Initialize your <code>uv</code> project:</strong>
This command sets up your Python project structure, creating essential files like <code>pyproject.toml</code> to manage dependencies.</p>
<pre><code class="lang-bash">uv init hello-world &amp;&amp; <span class="hljs-built_in">cd</span> hello-world
</code></pre>
</li>
<li><p><strong>Add your web framework dependency:</strong>
Use <code>uv add</code> to install either <code>fastapi</code> (with optional extras like <code>standard</code> for <code>uvicorn</code> and <code>pydantic</code>) or the classic <code>flask</code>.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># For FastAPI</span>
uv add <span class="hljs-string">"fastapi[standard]"</span>

<span class="hljs-comment"># Or for Flask</span>
uv add flask
</code></pre>
</li>
<li><p><strong>Create your application code:</strong>
Add starter code to <code>main.py</code>. Here's the example from the <a target="_blank" href="https://fastapi.tiangolo.com/#example">FastAPI documentation</a>:</p>
<pre><code class="lang-python"><span class="hljs-comment"># main.py</span>
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Union
<span class="hljs-keyword">from</span> fastapi <span class="hljs-keyword">import</span> FastAPI

app = FastAPI()

<span class="hljs-meta">@app.get("/")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">read_root</span>():</span>
    <span class="hljs-keyword">return</span> {<span class="hljs-string">"Hello"</span>: <span class="hljs-string">"World"</span>}

<span class="hljs-meta">@app.get("/items/{item_id}")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">read_item</span>(<span class="hljs-params">item_id: int, q: Union[str, None] = None</span>):</span>
    <span class="hljs-keyword">return</span> {<span class="hljs-string">"item_id"</span>: item_id, <span class="hljs-string">"q"</span>: q}
</code></pre>
</li>
<li><p><strong>Configure the start command for Nixpacks:</strong>
Create a <code>nixpacks.toml</code> file. This tells Nixpacks (and therefore Coolify) how to run your application inside the container.</p>
<pre><code class="lang-bash">touch nixpacks.toml
</code></pre>
<p>Add the following content. We use port <code>3000</code> as it's the default Coolify expects.</p>
<p><em>For FastAPI (using <code>uvicorn</code>):</em></p>
<pre><code class="lang-toml"><span class="hljs-comment"># nixpacks.toml</span>
<span class="hljs-section">[start]</span>
<span class="hljs-attr">cmd</span> = <span class="hljs-string">"fastapi run main.py --port 3000"</span>
</code></pre>
<p><em>For Flask (using <code>gunicorn</code>):</em></p>
<pre><code class="lang-toml"><span class="hljs-comment"># nixpacks.toml</span>
<span class="hljs-section">[start]</span>
<span class="hljs-attr">cmd</span> = <span class="hljs-string">"gunicorn -w 4 -b 0.0.0.0:3000 main:app"</span>
</code></pre>
</li>
<li><p><strong>Commit your code:</strong></p>
<pre><code class="lang-bash">git add .
git commit -m <span class="hljs-string">"Initial project setup"</span>
<span class="hljs-comment"># Push to your GitHub repository</span>
git push origin main
</code></pre>
</li>
<li><p><strong>Configure the application in Coolify:</strong></p>
<ul>
<li>Go to your Coolify dashboard and create a new application.</li>
<li>Select "GitHub" as the source and choose the repository you just pushed.</li>
<li>Coolify should automatically detect <code>Nixpacks</code> as the build pack and <code>3000</code> as the port based on your <code>nixpacks.toml</code>.</li>
</ul>
</li>
<li><p><strong>Deploy:</strong></p>
<ul>
<li>Click the "Deploy" button.</li>
<li>After the initial deployment, Coolify will automatically redeploy your application every time you push changes to your configured GitHub branch.</li>
</ul>
</li>
</ol>
<h2 id="heading-final-notes">Final Notes</h2>
<p>If you’re not using GitHub you can also configure webhooks in Coolify.</p>
<p>You probably want to add a <code>.gitignore</code> file for the <code>__pycache__</code> directory</p>
<p>This is written for Coolify v4. Since <a target="_blank" href="https://github.com/coollabsio/coolify/issues/5685">v5 was just announced</a> by the time you read this things on the Coolify side may have changed.</p>
<p>The source code for the Flask version is available <a target="_blank" href="https://github.com/rayberger/coolify-flask">here</a>.</p>
<p>Thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Presenting the OpenLibrary <> Wikidata Integration at Data Reuse Days 2025]]></title><description><![CDATA[I was invited to speak at Data Reuse Days 2025 and presented on February 27th. My talk was titled "169k Reasons to Care: Wikidata’s Role in Open Library Author Pages." I was finally inspired to post about it after visiting Orlando Code Camp.
Because ...]]></description><link>https://blog.rayberger.org/presenting-the-openlibrary-wikidata-integration-at-data-reuse-days-2025</link><guid isPermaLink="true">https://blog.rayberger.org/presenting-the-openlibrary-wikidata-integration-at-data-reuse-days-2025</guid><category><![CDATA[Open Library]]></category><category><![CDATA[Wikidata]]></category><category><![CDATA[software development]]></category><category><![CDATA[presentations]]></category><category><![CDATA[technology]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Mon, 07 Apr 2025 06:35:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1744007661650/631b056d-29a3-4e1a-aebd-4860a744b081.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I was invited to speak at <a target="_blank" href="https://www.wikidata.org/wiki/Event:Data_Reuse_Days_2025">Data Reuse Days 2025</a> and presented on February 27th. My talk was titled "169k Reasons to Care: Wikidata’s Role in Open Library Author Pages." I was finally inspired to post about it after <a target="_blank" href="https://blog.rayberger.org/orlando-code-camp-2025">visiting Orlando Code Camp</a>.</p>
<p>Because I was preparing for a move at the time, I submitted a pre-recorded video instead of presenting live. This turned out to be fortunate, as I encountered some technical difficulties during the live Q&amp;A portion – Jitsi indicated my microphone was active when it was actually muted!</p>
<p>Despite that small hiccup, I feel the presentation was successful. I was able to answer several questions and hopefully encouraged some attendees to check out Open Library.</p>
<p>You can watch the recording <a target="_blank" href="https://commons.wikimedia.org/wiki/File:Open_Library_-_Wikidata_Data_Reuse_Days_2025_Presentation.webm">here</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Orlando Code Camp 2025]]></title><description><![CDATA[I went to Orlando Code Camp on Saturday, primarily because my friend Diane was presenting, but also because I wanted to make new friends and reconnect with my techy side. Wow, did I succeed in those goals! Diane introduced me to her friend Lizzie, wh...]]></description><link>https://blog.rayberger.org/orlando-code-camp-2025</link><guid isPermaLink="true">https://blog.rayberger.org/orlando-code-camp-2025</guid><category><![CDATA[technology]]></category><category><![CDATA[conference]]></category><category><![CDATA[orlando]]></category><category><![CDATA[florida]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Mon, 07 Apr 2025 06:13:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1744005637745/13ead441-6f83-41db-9b77-268dcbc10630.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I went to Orlando Code Camp on Saturday, primarily because my friend Diane was presenting, but also because I wanted to make new friends and reconnect with my techy side. Wow, did I succeed in those goals! Diane introduced me to her friend <a target="_blank" href="https://lizziesiegle.com/">Lizzie</a>, who was also presenting and, more importantly, is a big YIMBY advocate in San Francisco. This connection was very timely because my partner is in SF right now networking with folks in the housing sector, and I’ll probably be moving there in the coming weeks.</p>
<p>Oh right, back to the conference. After waking up at 5:30 and driving two hours, I arrived just in time to sleep through the second half of the keynote. The talk wasn't boring, but rather that I was exhausted and needed to build some energy for socializing. I ended up seeing four talks, including Diane’s – which was so fun (and her first time speaking at a conference!) – and Lizzie’s about <a target="_blank" href="https://developers.cloudflare.com/durable-objects/">Cloudflare Durable Objects</a>, where I learned something completely new.</p>
<p>The crowd at the conference was friendly and nerdy (in the good way). I chatted briefly with a few strangers, but not being deeply immersed in the tech world, I felt a little shy about approaching people (the lack of sleep wasn't helping either). One person, however, talked with me quite a bit about government and technology. I told him about the street experiment where someone turned a parking spot into a temporary picnic area, which resulted in the police coming to remove the picnic table. His main response was, “Good! That’s for cars. We wouldn’t let a car take space in a park, so we shouldn’t allow a picnic table in a parking spot.” At that moment, it hit me that I was in a place where nearly everyone relies on cars and seems happy to keep it that way. And now, as I write this, I realize this difference in perspective is exactly what the Lab of Thought is trying to address: opening people’s minds to alternatives. That’s relatively easy to do in progressive cities or with fellow urbanists, but I was woefully unprepared for even the mild resistance my anecdote faced there.</p>
<p>Alas, Orlando Code Camp was a great experience. I made a new friend, celebrated an old friend, and reignited some excitement for both software and urbanism.</p>
<p>PS: The banner photo is of the ending ceremony where they gave away prizes</p>
]]></content:encoded></item><item><title><![CDATA[Polishing Nextcloud Tasks]]></title><description><![CDATA[Background
Nextcloud is an open-source alternative to Google Drive. I use it because I value control over my data and appreciate its use of open standards. While many people complain about Nextcloud, if they invested that time in reporting issues or ...]]></description><link>https://blog.rayberger.org/polishing-nextcloud-tasks</link><guid isPermaLink="true">https://blog.rayberger.org/polishing-nextcloud-tasks</guid><category><![CDATA[software development]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[Nextcloud]]></category><category><![CDATA[technology]]></category><category><![CDATA[CSS]]></category><category><![CDATA[polish]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Sun, 02 Feb 2025 08:44:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738485733228/47835c92-435a-4669-baef-76f4665d6e36.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-background">Background</h2>
<p>Nextcloud is an open-source alternative to Google Drive. I use it because I value control over my data and appreciate its use of open standards. While many people complain about Nextcloud, if they invested that time in reporting issues or contributing fixes, the platform would be much better.</p>
<p>Earlier this year, while working on my thesis, I began using <a target="_blank" href="https://apps.nextcloud.com/apps/tasks">Nextcloud Tasks</a> extensively. I encountered some minor jankiness and decided to do something other than complain. I reported three bugs and even fixed one of them. Here's a summary of my contributions:</p>
<h2 id="heading-1-navigation-menu-sidebar-css-transition-is-janky-with-sharp-corners">1. Navigation Menu (sidebar) CSS transition is janky with sharp corners</h2>
<ul>
<li><p><a target="_blank" href="https://github.com/nextcloud/server/issues/43969">Reported</a> on March 3, 2024</p>
</li>
<li><p><a target="_blank" href="https://github.com/nextcloud-libraries/nextcloud-vue/pull/5389">My fix was merged</a> on March 20, 2024</p>
</li>
</ul>
<p><img src="https://private-user-images.githubusercontent.com/921217/309588959-c4fa2ada-deae-4ba4-92c9-2b506e0c6007.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mzg0ODU2MDQsIm5iZiI6MTczODQ4NTMwNCwicGF0aCI6Ii85MjEyMTcvMzA5NTg4OTU5LWM0ZmEyYWRhLWRlYWUtNGJhNC05MmM5LTJiNTA2ZTBjNjAwNy5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMjAyJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDIwMlQwODM1MDRaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1kYThhYjFmN2E4YzBmZmU4YjEwYTMzZDQ1NmUwN2JmMzU5OGRkMDE0MGM1MGEzZDBjZGVlODFkZDQ2OGZjZDI2JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9._1czTuLzZm6AmKjq3JvjfaS6sV6naIBr5z-dbtm-0SY" alt="image" /></p>
<h2 id="heading-2-three-dots-menu-button-on-task-details-sidebar-always-looks-selected">2. Three-dots menu button on task details sidebar always looks selected</h2>
<ul>
<li><p><a target="_blank" href="https://github.com/nextcloud-libraries/nextcloud-vue/issues/5363">Reported</a> on March 7, 2024</p>
</li>
<li><p>Fixed on March 20, 2024</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738485451379/9e59e18e-6caa-4cb8-b8d7-9ad2a43903a5.webp" alt class="image--center mx-auto" /></p>
<h2 id="heading-3-remove-extra-css-margin-for-url-in-task-title">3. Remove extra CSS margin for URL in task title</h2>
<ul>
<li><p><a target="_blank" href="https://github.com/nextcloud/tasks/issues/2500">Initially reported</a> on March 1, 2024</p>
</li>
<li><p><a target="_blank" href="https://github.com/nextcloud/server/issues/44364">Reported on a different repo</a> on March 20, 2024</p>
</li>
<li><p>Fixed on May 4, 2024</p>
</li>
</ul>
<p><img src="https://private-user-images.githubusercontent.com/921217/309242222-43709fb5-5b14-4260-b92b-7c0d14d33e9b.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mzg0ODU0NzcsIm5iZiI6MTczODQ4NTE3NywicGF0aCI6Ii85MjEyMTcvMzA5MjQyMjIyLTQzNzA5ZmI1LTViMTQtNDI2MC1iOTJiLTdjMGQxNGQzM2U5Yi5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMjAyJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDIwMlQwODMyNTdaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT03Y2JjOTNjNWY2NjAzYzBmNjkwZjgyM2FkZWM3Y2JlZjk5NDViODQ2MDE4M2IwMGE2OGEwNzFkYTQyYzBlNzhlJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.PFFZFEL0Gl33d_zVt2YtXkIviIX-tMV52oPhdugyXxc" alt="image" /></p>
<hr />
<p>If you want open-source software to be good, help make it better. Sometimes, it's as simple as filing a bug report.</p>
<p>In the near future, I want to do a deep-dive comparison of Nextcloud Tasks and TickTick to see how we can polish the UI for Tasks and make it feel smoother overall.</p>
]]></content:encoded></item><item><title><![CDATA[Vue Migration Part 2 - A Simple Solution Emerges]]></title><description><![CDATA[In Part 1 of our Vue migration journey at Open Library, I expressed frustration with the tooling's lack of support for our specific use case. However, I'm pleased to report that we've found an elegant solution. You can view the final implementation i...]]></description><link>https://blog.rayberger.org/vue-migration-part-2</link><guid isPermaLink="true">https://blog.rayberger.org/vue-migration-part-2</guid><category><![CDATA[Vue.js]]></category><category><![CDATA[vite]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[technology]]></category><category><![CDATA[software development]]></category><category><![CDATA[Open Library]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Sun, 02 Feb 2025 06:05:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738476287878/ebde7c62-916c-4dba-8f81-76d827e15d1e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In <a target="_blank" href="https://blog.rayberger.org/vue-3-web-components-open-library">Part 1</a> of our Vue migration journey at Open Library, I expressed frustration with the tooling's lack of support for our specific use case. However, I'm pleased to report that we've found an elegant solution. You can view the final implementation in <a target="_blank" href="https://github.com/internetarchive/openlibrary/pull/10372">this pull request</a>.</p>
<p>Seeking feedback, I turned to Reddit, where a question about the necessity of inline dynamic imports set me on the right path. I realized we weren't using dynamic imports at all. I had initially enabled them as required for IIFE mode, which I had chosen for its compatibility with older browsers.</p>
<p>There's a better method for supporting legacy browsers while maintaining modern functionality. The solution is to use the legacy plugin and create two separate builds: a modern version and a legacy fallback. It looks like this for the client:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"{modern_build}"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-comment">&lt;!-- Fallback for legacy browsers --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">nomodule</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"{polyfills_build}"</span> <span class="hljs-attr">defer</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">nomodule</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"{legacy_build}"</span> <span class="hljs-attr">defer</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>This approach offers several advantages:</p>
<ol>
<li>It produces smaller file sizes.</li>
<li>It extends support to an even broader range of older browsers.</li>
<li>Modern browsers use the optimized build, while legacy browsers automatically fall back to the compatible version.</li>
</ol>
<p>With this improved setup, we can now utilize multiple inputs and outputs in our build process, eliminating the need to run the build tool separately for each component.</p>
<p>As a result, our <code>vite.config.js</code> file has been simplified:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> buildInput = {};
componentNames.forEach(<span class="hljs-function"><span class="hljs-params">name</span> =&gt;</span> { buildInput[name] = getTemporaryVueInputPath(name) });

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
    <span class="hljs-attr">plugins</span>: [
        vue({ <span class="hljs-attr">customElement</span>: <span class="hljs-literal">true</span> }),
        legacy({ <span class="hljs-attr">targets</span>: [<span class="hljs-string">'defaults'</span>, <span class="hljs-string">'not IE 11'</span>] })
    ],
    <span class="hljs-attr">build</span>: {
        <span class="hljs-attr">outDir</span>: join(BUILD_DIR, <span class="hljs-string">'/production'</span>),
        <span class="hljs-attr">rollupOptions</span>: {
            <span class="hljs-attr">input</span>: buildInput,
            <span class="hljs-attr">output</span>: {
                <span class="hljs-attr">entryFileNames</span>: <span class="hljs-string">'ol-[name].js'</span>,
                <span class="hljs-attr">format</span>: <span class="hljs-string">'es'</span>
            },
        },
    },
});
</code></pre>
<p>While my issue reports suggesting improvements to the documentation remain unaddressed, I hope these blog posts can assist others facing similar challenges.</p>
<p><em>Found this helpful? Consider joining our <a target="_blank" href="https://github.com/internetarchive/openlibrary">Open Library</a> community as a dev, librarian, designer, or in any other way you want to help.</em></p>
]]></content:encoded></item><item><title><![CDATA[2024 in Review]]></title><description><![CDATA[Previously: 2023 in Review
I want to be better at reflecting and want to remember these wonderful years. So here I am, at the end of January, writing my 2024 in review. I was inspired by Steve and Shreyans’ blog posts and actually finishing the YearC...]]></description><link>https://blog.rayberger.org/2024-in-review</link><guid isPermaLink="true">https://blog.rayberger.org/2024-in-review</guid><category><![CDATA[personal]]></category><category><![CDATA[2024]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Fri, 31 Jan 2025 03:06:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738349631878/4563687a-3234-4690-900e-49e6e39d8a19.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Previously: <a target="_blank" href="https://blog.rayberger.org/2023-in-review">2023 in Review</a></p>
<p>I want to be better at reflecting and want to remember these wonderful years. So here I am, at the end of January, writing my 2024 in review. I was inspired by <a target="_blank" href="https://www.stevegattuso.me/2024/12/31/2024-in-review.html">Steve</a> and <a target="_blank" href="https://shreyans.org/2024">Shreyans’</a> blog posts and actually finishing the <a target="_blank" href="https://yearcompass.com/">YearCompass</a> first.</p>
<p>This year I lived in Amsterdam 🇳🇱, Oviedo (Spain) 🇪🇸, Thailand (just 3 weeks) 🇹🇭, and Bogota 🇨🇴.</p>
<p>🎓 I completed my MSc in Urban Studies (<a target="_blank" href="https://www.4cities.eu/">4CITIES</a>). I learned a lot and now have well informed opinions on a few things now. The few months of focused writing on the thesis were hell but I’m pretty happy with the result: <a target="_blank" href="https://doi.org/10.25365/thesis.77214">The Uneasy Emergence of the Edible Insect Industry in the Netherlands</a>. Still figuring out how to bridge urbanism and tech for a career.</p>
<p><a target="_blank" href="https://fosdem.org/2024/">FOSDEM</a> in Brussels was fun, volunteering at <a target="_blank" href="https://www.mozillafestival.org/en/highlights/mozfest-house-amsterdam-2024/">MozFest Amsterdam</a> was a blast, interning with <a target="_blank" href="https://www.thelabofthought.co/">the Lab of Thought</a> was a bit lonely but I made some great connections, I loved the “Dance to Share” class I took at CREA, making dumplings with a big group for Chinese New Year was chaotic bliss, visiting the Internet Archive in SF turned so many little faces from Zoom into real people, attending the 50 years of Ciclovia celebration got me fired up for making change, and <a target="_blank" href="https://en.wikipedia.org/wiki/Novena_of_aguinaldos">Novenas</a> and Christmas in Bogota gave me a great appreciation for how family makes the holidays special.</p>
<p>I had five intentions last year.</p>
<ol>
<li><p><strong>Open Source Contributions</strong>: Made lots of contributions, shared it in blogs, and I’m generally happy about it. Open Library has been a great community to be part of.</p>
</li>
<li><p><strong>Cooking</strong>: Improved significantly, mastered dishes like creamy chicken pasta, and cooked (more) frequently, especially towards the end of the year.</p>
</li>
<li><p><strong>Spanish Language Learning</strong>: Spent a solid part of the year working on it every day (loved Dreaming Spanish videos) and was able to hold my own during the holidays in Bogota. Still not where I’d like to be though.</p>
</li>
<li><p><strong>Social Life</strong>: Maintained an active social life, especially in Amsterdam, with regular dinners and quality time with friends. After Amsterdam, not so much thanks to the thesis.</p>
</li>
<li><p><strong>Reading &amp; Writing</strong>: I wrote 11 blog posts, pretty good, but fell short on reading often (outside of thesis). I’ll do better this year.</p>
</li>
</ol>
<p>Overall, I feel I did alright on the intentions, which were deeply disrupted by the thesis months. I think I’ll carry a few of them forward.</p>
<p>Finally, I’ll say it has been a pretty great year. I thoroughly enjoyed my time in Amsterdam and deeply appreciate the friends I made there. My year was also made oh so much better by the friends who visited including Alex, Sam, Sam &amp; Cliff, Zoe, Kevin, and more. All that being said, I can’t imagine how this year would have been if I didn’t have my wonderful partner Maria Paula by my side through it all.</p>
<p>Lets make 2025 even better!</p>
<h1 id="heading-selected-photos">Selected Photos</h1>
<p>Here are some photos, the bring cozy memories to me. Notably, excluding photos with people because there are too many good ones (and something about AI).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738291826176/9194c98e-cee3-4936-b536-eebd0392764c.jpeg" alt class="image--center mx-auto" /></p>
<p>The beautiful Amsterdam apartment where we hosted many dinners and friends.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738291875334/4071eeb1-c2e0-4aa2-8d40-10a7299488d3.jpeg" alt class="image--center mx-auto" /></p>
<p>The view of the University of Amsterdam I enjoyed so many days.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738291896939/43bd8e12-d160-4323-8704-1f4df7a98f01.jpeg" alt class="image--center mx-auto" /></p>
<p>Enjoying some beetle beer on the canal in Amsterdam.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738291919951/00386b42-002d-4a54-8998-dcaab1eb5a14.jpeg" alt class="image--center mx-auto" /></p>
<p>The ominous datacenter I saw during a tour for MozFest.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738291942916/04cbe1f8-2bdc-40ad-a103-cfc3b3eaf730.jpeg" alt class="image--center mx-auto" /></p>
<p>A beautiful beach gathering in the North of Spain (beach is behind me).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738292054053/4efd0d18-bef1-48b4-954b-d0697ce7cc36.jpeg" alt class="image--center mx-auto" /></p>
<p>A countryside bike tour in Chiang Mai, Thailand.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738292082210/139e6e4b-1c40-4269-8362-8f51ab1b9edf.jpeg" alt class="image--center mx-auto" /></p>
<p>The locals in Colombia wondering what a gringo is doing way out here.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738292122100/53527c6b-6796-4565-a7c6-d638bef1da9d.jpeg" alt class="image--center mx-auto" /></p>
<p>How I felt after weeks of celebrating Novenas, Christmas, and New Years, (also a photo of Olivia the cat).</p>
]]></content:encoded></item><item><title><![CDATA[Migrating Web Components from Vue 2 to Vue 3 at Open Library]]></title><description><![CDATA[Update Feb, 2025: Vue Migration Part 2 - A Simple Solution Emerges

TL;DR: Migrating Vue 2 Web Components to Vue 3 isn't as straightforward as the docs suggest. We tackled three main challenges: setting up Vite builds, managing multiple components, a...]]></description><link>https://blog.rayberger.org/vue-3-web-components-open-library</link><guid isPermaLink="true">https://blog.rayberger.org/vue-3-web-components-open-library</guid><category><![CDATA[Vue.js]]></category><category><![CDATA[vite]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[technology]]></category><category><![CDATA[software development]]></category><category><![CDATA[Open Library]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Sat, 18 Jan 2025 00:56:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1737060249149/44e5c0df-3d73-47bb-876c-fc04857a9e1a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Update Feb, 2025: <a target="_blank" href="https://blog.rayberger.org/vue-migration-part-2">Vue Migration Part 2 - A Simple Solution Emerges</a></em></p>
<blockquote>
<p>TL;DR: Migrating Vue 2 Web Components to Vue 3 isn't as straightforward as the docs suggest. We tackled three main challenges: setting up Vite builds, managing multiple components, and handling plugins.</p>
</blockquote>
<p>You can see the final pull request <a target="_blank" href="https://github.com/internetarchive/openlibrary/pull/10298/">here</a>.</p>
<p>This is the story of how a seemingly simple migration turned into a 20-hour adventure through the world of Vite and Vue 3. If you're considering a similar migration or just curious about the state of Vue 3 <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components">Web Components</a>, this is for you.</p>
<h1 id="heading-understanding-vue-and-web-components">Understanding Vue and Web Components</h1>
<p>In contrast to typical Single Page Applications (SPAs), where Vue renders the entire application, <a target="_blank" href="https://openlibrary.org">Open Library</a> leverages Vue for specific interactive elements within traditional HTML pages. This is accomplished by integrating custom HTML tags, or Web Components, which are then transformed into Vue components.</p>
<p>For example, when you look at the source of an Open Library page, you might see an <code>&lt;ol-barcode-scanner&gt;</code> tag. This custom tag, along with its JavaScript, allows Vue to render just that specific element rather than managing the entire page. It's like having mini Vue apps scattered throughout traditional HTML pages.</p>
<h2 id="heading-vue-3-migration-challenges">Vue 3 Migration Challenges</h2>
<p>While Vue 3 is largely compatible with Vue 2, and there are many resources available for migrating from Vue 2 to Vue 3, our challenge lay in building web components in Vue 3. The Vue 3 documentation covers Web Components, but the approach was not directly applicable to our use case, which involves building one component per page with isolated scripts.</p>
<h2 id="heading-vue-2-build-setup">Vue 2 Build Setup</h2>
<p>The process of building Web Components for Vue 2 was very straightforward. We would simply run <code>vue-cli-service build BarcodeScanner.vue</code>, and a JS file would be generated that we could use on any page.</p>
<h1 id="heading-challenges-with-vue-3-web-components">Challenges with Vue 3 Web Components</h1>
<p>Let me break down our journey into three main challenges. While each challenge has a straightforward solution in hindsight, finding these solutions took considerable exploration.</p>
<h2 id="heading-building-one-web-component-with-vite">Building one Web Component with Vite</h2>
<p>The <a target="_blank" href="https://cli.vuejs.org/">deprecated Vue CLI</a> we used to build Vue 2 Web Components does not support Vue 3 Web Components and has not been updated in years. As a result, I investigated the latest and greatest build tool: Vite. There's one big difference with Vite: it doesn't accept <code>.vue</code> files (Single File Components) like Vue CLI did. Instead, Vite requires a configuration file as its input.</p>
<p>This posed a challenge because I was 1) disbelieving that it couldn't take .vue files and instead required two new files for every component, 2) trying to get it to create all the components without so many new files, and 3) attempting to get a Vue plugin working (more on that later). That being said, the docs are fairly straightforward on how to do this for just one component. I just needed two new files:</p>
<pre><code class="lang-js"><span class="hljs-comment">// vite.config.js</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
    <span class="hljs-attr">plugins</span>: [vue({ <span class="hljs-attr">customElement</span>: <span class="hljs-literal">true</span> })], <span class="hljs-comment">// Because all of our Vue components are customElements</span>
    <span class="hljs-attr">build</span>: {
        <span class="hljs-attr">outDir</span>: <span class="hljs-string">'PRODUCTION_DIR'</span>,
        <span class="hljs-attr">emptyOutDir</span>: <span class="hljs-literal">false</span>, <span class="hljs-comment">// Preserve existing files since we build components individually</span>
        <span class="hljs-attr">target</span>: <span class="hljs-string">'es2015'</span>, <span class="hljs-comment">// The oldest browsers Vite supports out of the box</span>
        <span class="hljs-attr">rollupOptions</span>: {
            <span class="hljs-attr">input</span>: join(BUILD_DIR, <span class="hljs-string">`BarcodeScanner.js`</span>),
            <span class="hljs-attr">output</span>: {
                <span class="hljs-attr">entryFileNames</span>: <span class="hljs-string">`ol-barcode-scanner.js`</span>,
                <span class="hljs-attr">inlineDynamicImports</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// needed for components to work with just one js file</span>
                <span class="hljs-attr">format</span>: <span class="hljs-string">'iife'</span> <span class="hljs-comment">// use iife to support old browsers without type="module"</span>
            },
        },
    },
});
</code></pre>
<pre><code class="lang-js"><span class="hljs-comment">// BarcodeScanner.js</span>
<span class="hljs-keyword">import</span> { defineCustomElement } <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>;
<span class="hljs-keyword">import</span> ele <span class="hljs-keyword">from</span> <span class="hljs-string">'./BarcodeScanner.vue'</span>;
customElements.define(<span class="hljs-string">'ol-barcode-scanner'</span>, defineCustomElement(ele));
</code></pre>
<h2 id="heading-building-multiple-web-components">Building multiple Web Components</h2>
<p>At this point, we have a Vite config file that can only have one input, which is a JavaScript file, and it produces a single output, the desired JavaScript file. This limitation poses a challenge since we have numerous components to build. While Vite config files support multiple input and output setups, this functionality is not available when using the <a target="_blank" href="https://rollupjs.org/configuration-options/#output-inlinedynamicimports">inlineDynamicImports</a> option, which we need for the components to work in isolation.</p>
<p>The problem we face is that we need a single configuration file to be mapped to one JavaScript file, which in turn is mapped to one Vue file. This setup seems overly complicated. Three files now for each component instead of just one .vue file. The two new files are basically identical for each component. There has to be a way around this, right?</p>
<p><a target="_blank" href="https://mermaid.live/edit#pako:eNqFU11P20AQ_Cure44tH05C8ANS84GE1EqVgIqQRNX5vEmusu-s-yCkcf47Zx80UIrqB2u8O7M7HvsOhKsCSUbWpdrxLdMWbqdLCf4K9_vFmOmWc8OZlKjjR4criKLL5jszBguwCiZfrxuYL3wn4qWIDOpHwRFyJ8piFcbMg0SrwnE0UHfACiVhLUps4GGhyigPmyLzsuqXWb218uVTK9dVrbT1ZvJ9A-PAnnR-dphHXPm2RGmjnWZ1jTqIbhU4g-BZUJduI6TJQLwb9Pc-IWtnT67G3Zi7NgRmoGs2MP1gUliMuZJrsTlJp_9KcLZoue9im_0ntqtPYwt64_KNf-ct_HB45RUmlLs0T_A-QJTFB924NfNzoqqKyeKNenaC8z9q0iMV6oqJwv9Qh7a8JHaLFS5J5mHOjEdLefQ85qy62UtOMqsd9ohWbrMl2ZqVxj-5umAWp4J5E9UrpWaSZAfyRDJK07hPaTI6T9IhTc9GtEf2vpzEyWDQT0dJnw59j54de-S3Un5CEl-kCaUXw6STDc8H3byHrvmyFAthlf4WjkP4YOT4DKMUAaU"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737059841771/fbc0cade-6441-430e-929d-7b98d523e330.png" alt class="image--center mx-auto" /></a></p>
<p>The left half is the old Vue CLI setup, the right half is the new vite setup.</p>
<h3 id="heading-trial-and-error">Trial and Error</h3>
<p>Through reading docs, trial and error, and ChatGPT, we painfully accepted that we indeed need a Vue file, a JavaScript file that points to the Vue file, and a config file that points to the JavaScript file (please someone come and show me how I'm wrong). If we were to approach this in a traditional manner, we would require a Vue file, a JavaScript file, and a Vite config file for each component. Currently, we have five components, which means we would end up adding ten new files that are essentially identical. There has to be a way around this, right?</p>
<h3 id="heading-solution">Solution</h3>
<p>It seems too hacky for some of the best webdev tools (Vue and Vite), but I found a solution that actually worked without adding 10 new files to the codebase. It comes in two parts.</p>
<p>First, set an environment variable <code>COMPONENT_NAME</code> that <code>vite.config.js</code> reads so that we don't need one config file for every component:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> COMPONENT_NAME = process.env.COMPONENT;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
    <span class="hljs-attr">build</span>: {
        <span class="hljs-comment">/// shorted to show the change</span>
        <span class="hljs-attr">rollupOptions</span>: {
            <span class="hljs-attr">input</span>: join(BUILD_DIR, <span class="hljs-string">`vue-tmp-<span class="hljs-subst">${COMPONENT_NAME}</span>.js`</span>),
            <span class="hljs-attr">output</span>: { <span class="hljs-attr">entryFileNames</span>: <span class="hljs-string">`ol-<span class="hljs-subst">${COMPONENT_NAME}</span>.js`</span>, },
        },
    },
});
</code></pre>
<p>Second, we need to deal with these pesky input files. So we just generate them from a string... in the config file itself.</p>
<pre><code class="lang-js">
generateViteEntryFile(COMPONENT_NAME);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({...})

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateViteEntryFile</span>(<span class="hljs-params">componentName</span>) </span>{
    <span class="hljs-keyword">const</span> template = <span class="hljs-string">`
import { defineCustomElement } from 'vue';
import ele from './<span class="hljs-subst">${componentName}</span>.vue';
customElements.define('<span class="hljs-subst">${kebabCase(componentName)}</span>', defineCustomElement(ele));
`</span>;

    <span class="hljs-keyword">try</span> {
        writeFileSync(join(BUILD_DIR, <span class="hljs-string">`vue-tmp-<span class="hljs-subst">${componentName}</span>.js`</span>), template);
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-comment">// eslint-disable-next-line no-console</span>
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">`Failed to generate Vite entry file: <span class="hljs-subst">${error.message}</span>`</span>);
        process.exit(<span class="hljs-number">1</span>);
    }
}
</code></pre>
<p>All things considered, this solution is simple and keeps us from having many nearly identical files to keep in sync. That being said, I really can't believe these hacks (environment variables and generated input files) are the best way.</p>
<h2 id="heading-plugin-support">Plugin Support</h2>
<p>Everything was functioning smoothly for all of our components, except for one that utilizes a Vue plugin: <code>vue-async-computed</code>. We previously considered removing this plugin since it didn't support Vue 3, but we ultimately decided against it because it helps maintain a clean structure in our code. Drini actually made the <a target="_blank" href="https://github.com/foxbenjaminfox/vue-async-computed/pull/124">PR to add Vue 3 support</a> years ago. Given the lack of updates to that plugin, maybe it's worth reconsidering switching to newer alternatives like <a target="_blank" href="https://vueuse.org/core/computedAsync/">computedAsync</a>, but dang it, I'm on a mission to upgrade to Vue 3. Besides, Drini made a good point that there are nice plugins we should consider using after the upgrade.</p>
<p>I explored various approaches to integrate the plugin into our Vue component. The guides I found often went into Vue's internals, particularly regarding how to incorporate the plugin for the component. Our team does not consist of Vue experts; we simply want a little more interactivity on parts of the site.</p>
<p>Ultimately, I decided to use the <code>vue-web-component-wrapper</code> library. To be fair, I saw it earlier, but resisted adding another dependency for something that should be easy. Anyway, the library simplifies the process by allowing us to pass in the desired plugin along with our component. Short and simple, and I even added a conditional so the plugin is only added for the one component that needs it.</p>
<h1 id="heading-summary">Summary</h1>
<p>I faced many challenges while migrating from Vue 2 Web Components to Vue 3 Web Components. However, they don't seem too hard now that we have solutions.</p>
<p>I am certain that there are technical reasons for the difficulties encountered during this migration. However, if I had a magic wand, here are the changes I would like:</p>
<ol>
<li><p>Vite (rollup under the hood) should allow for multiple inputs and outputs while utilizing <code>inlineDynamicImports</code>. This feature alone would have greatly simplified the entire process. If this cannot be implemented, then the <a target="_blank" href="https://vuejs.org/guide/extras/web-components">Web Components guide</a> should be updated to include a reasonable workaround. Related issue <a target="_blank" href="https://github.com/rollup/rollup/issues/5601">here</a>.</p>
</li>
<li><p>The Web Component guide should be updated to include information about plugins. It would be helpful to explain how to add them without introducing another dependency, or at the very least, to point to <code>vue-web-component-wrapper</code> as a viable solution. Issue opened <a target="_blank" href="https://github.com/vuejs/docs/issues/3151">here</a>.</p>
</li>
<li><p>Vite should be able to accept <code>.vue</code> files in the <code>vite.config.js</code>.</p>
</li>
</ol>
<p>Overall, this migration process required approximately 20 hours of my effort (not counting the attempts by other folks in previous years). Throughout this process, I did not find a single example that addressed all of these challenges. I hope that this account will be helpful to those poor souls who find themselves migrating from Vue 2 to Vue 3 in 2025 and beyond. Perhaps the Vue and Vite teams will take note of this and improve the official guide and possibly even the tooling. Happy open sourcing!</p>
<p><em>Found this helpful? Consider joining our</em> <a target="_blank" href="https://github.com/internetarchive/openlibrary"><em>Open Library</em></a> <em>community as a dev, librarian, designer, or any other way you want to help.</em></p>
<p>PS: Huge thanks to Drini for reviewing and merging this pull request!</p>
]]></content:encoded></item><item><title><![CDATA[Cleaning Up Legacy Data: From Wikipedia Links to Wikidata IDs in Open Library]]></title><description><![CDATA[Software developers often avoid maintenance work, partly because companies rarely incentivize it. This is one reason why some tech companies have a reputation for abandoning products after launch. But maintenance is important, which is why I'm doing ...]]></description><link>https://blog.rayberger.org/cleaning-up-legacy-wikipedia-links</link><guid isPermaLink="true">https://blog.rayberger.org/cleaning-up-legacy-wikipedia-links</guid><category><![CDATA[Open Library]]></category><category><![CDATA[Wikipedia]]></category><category><![CDATA[Wikidata]]></category><category><![CDATA[tech ]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Tue, 14 Jan 2025 02:25:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736821446153/a5e4aa27-c078-4d3a-9913-6ecb722bf40d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Software developers often avoid maintenance work, partly because companies rarely incentivize it. This is one reason why some tech companies have a reputation for abandoning products after launch. But maintenance is important, which is why I'm doing some data cleanup work after a recent feature.</p>
<h1 id="heading-context">Context</h1>
<p>I recently shipped a feature for Open Library that displays Wikidata (a Wikipedia sister project) information on author pages, including Wikipedia links. This enhancement automatically shows Wikipedia links in the correct language and updates when new Wikipedia pages are created about an author - as long as we have their Wikidata ID.</p>
<h1 id="heading-the-problem">The Problem</h1>
<p>A long time ago, Open Library had a field on authors specifically for Wikipedia links. While users can no longer add or edit these legacy Wikipedia links through the UI (they now use a generic links field instead), the old links are still visible. Simply moving these links to the generic field would work, but we'd lose the benefits of language-aware links and other Wikidata integration features we're planning. That and a bunch of the links were broken anyway.</p>
<h1 id="heading-the-solution">The Solution</h1>
<p>Replace the old Wikipedia field entries with their corresponding Wikidata IDs.</p>
<p>Implementation Steps:</p>
<ol>
<li><p>Download the Open Library author data dump</p>
</li>
<li><p>Query the dump to find authors with Wikipedia links</p>
</li>
<li><p>Use the Open Library API to add Wikidata IDs and remove Wikipedia links</p>
</li>
</ol>
<p>Here's the query I used to extract authors and their Wikipedia links: <code>SELECT column1 as key, json_extract(column4, '$.wikipedia') as wikipedia FROM read_csv('~/Downloads/ol_dump_2024-07-31/ol_dump_authors_2024-07-31.txt.gz') where wikipedia is not null limit 1;</code></p>
<p>The dump revealed 1,786 authors with Wikipedia links - a tad too many to manage in spreadsheets. So, with the help of AI, I built a small tool to help review them quickly.</p>
<h2 id="heading-the-tool">The Tool</h2>
<p>I built the review tool using Vue.js, Tailwind, and PocketBase. I chose this stack primarily to experiment with PocketBase, which I'm considering for future projects. It's an excellent solution for simple database hosting. I also wanted to try <a target="_blank" href="https://pockethost.io">PocketHost</a>, though they've since removed their free tier (fortunately, I'm grandfathered in).</p>
<p>For importing data into PocketBase, I used <a target="_blank" href="https://github.com/michal-kapala/pocketbase-import">pocketbase-import</a>. The initial import was slow, so I <a target="_blank" href="https://github.com/michal-kapala/pocketbase-import/issues/1">requested</a> batch import capability from the creator, which they added soon after.</p>
<p>While using the tool, I discovered many authors already had correct Wikidata IDs and just needed their redundant Wikipedia links removed. I wrote a script to extract Wikidata IDs from Wikipedia links, which helped identify cases where we could simply remove the Wikipedia link without manual review.</p>
<p>This automation reduced the manual review cases to 209 authors - much more manageable! During the review process, I also identified and merged duplicate author records using the <a target="_blank" href="https://www.rayberger.org/onepagers/sites/ol-quickstatements/">Open Library QuickStatements tool</a> I've been developing.</p>
<p>After processing these records, I found a small oversight in my query logic. I had been comparing <code>QID != QID_from_wikipedia</code>, but needed to manually review cases where both fields were null.</p>
<p>To ensure completeness, I wrote a verification script to check the live data for any missed Wikipedia links. This caught a few redirects from merged authors, which I fixed.</p>
<p>Now Open Library is a little easier to use and the data is a little cleaner!</p>
<p>Video walkthrough: https://youtu.be/QQRKMWFK5yE</p>
<p>Next Steps:</p>
<ul>
<li><p>Run the query again after the next data dump to catch any new Wikipedia links</p>
</li>
<li><p>Submit a PR to remove the old Wikipedia logic from Open Library</p>
</li>
<li><p>Remove Wikipedia field from the Open Library schema <a target="_blank" href="https://github.com/internetarchive/openlibrary-client/blob/master/olclient/schemata/author.schema.json">here</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[A Morning in Bogota]]></title><description><![CDATA[What does a morning in Bogotá look like for me? This photo captures a snapshot of my daily life over the past few months. For breakfast, I cooked a rice paper omelet, topped with habanero-mayonnaise and a drizzle of agave syrup. Green onions were sor...]]></description><link>https://blog.rayberger.org/a-morning-in-bogota</link><guid isPermaLink="true">https://blog.rayberger.org/a-morning-in-bogota</guid><category><![CDATA[Colombia]]></category><category><![CDATA[bogota]]></category><category><![CDATA[calm]]></category><category><![CDATA[morning]]></category><category><![CDATA[urban]]></category><category><![CDATA[daily life]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Fri, 10 Jan 2025 23:57:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736553600641/bd5f0a25-85db-4779-aa19-37d936fda382.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>What does a morning in Bogotá look like for me? This photo captures a snapshot of my daily life over the past few months. For breakfast, I cooked a rice paper omelet, topped with habanero-mayonnaise and a drizzle of agave syrup. Green onions were sorely missed.</p>
<p>The aftermath of breakfast lingers on our 3D animal placemats - well-used plates and a crumpled napkin keeping company with two ceramic coasters, gifted from a friend after her visit to Spain.</p>
<p>There are pink roses that I got for Mapy before she returned from visiting the doctor's office about her knee. They sit in a one-liter kettle, the only thing we have that can accommodate them.</p>
<p>There is also an incense holder with a few ashes and a small bag of napkins that forever slides around the table. The table and chairs are plastic patio furniture. Like almost everything here, they came with the house.</p>
<p>Looking beyond the table, we see the window that I love to look out. The best part is the dogs across the way running in the field, playing.</p>
<p>This particular park seems to be the one for dog families. The park a few hundred meters in the opposite direction seems to be one for kid families.</p>
<p>The worst part is the two withering trees in the center. Not because they’re drying, because they form the informal public urinal for all of the gig workers, taxi drivers, or anyone in a rush.</p>
<p>Between the trees and the park lies a large and, usually, dry canal. Just on the other side of the canal is a street that few people walk down, but cars frequently pass, and a long queue builds every rush hour.</p>
<p>However, the street doesn't go unused; most days there are a few recicladores that set up camp on the grass with their dogs. They're part of the informal waste system of Bogota, collecting and sorting cardboard and other waste to be sold.</p>
<p>They often have a small bonfire to stay warm and cook a snack. A quiet group, they always seem to be working. Even though the mess they leave behind is displeasing to the eye, there is a pleasure from observing this family, or group of friends, working and playing, and making the best with what they have.</p>
<p>Through the doorway there is a wind-beaten turquoise tarp. It is a visual reminder of the primary sound I hear throughout the day, which is the work from the construction crew that put up that tarp and are putting together a new building. So far, it seems rather well put together, unlike some of the shoddily constructed houses around. They start work early with their hammers, drills, and trucks. I don’t mind it much… I guess it is the sound of progress.</p>
<p>Between the construction site and my apartment is another building with an enclosed driveway housing nine perritos (little dogs). When the sun shines bright, I love to sit just outside the doorway, playing a daily game with my unseen neighbors - sending a "woof woof" floating across the air, guaranteed to trigger a chorus of excited yips and barks.</p>
<p>This is the view I have seen and enjoyed the most these past few years of travel. I have much peace, pleasure, and respect for this quaint community I have observed and become a part of.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736553285418/9e9e29c9-fd34-4030-9aac-b2a867f48e7a.jpeg" alt class="image--center mx-auto" /></p>
<p>Photo taken on January 8, 2025 in Cedritos, Bogota, Colombia.</p>
]]></content:encoded></item><item><title><![CDATA[My First Wikidata QuickStatements Project: Documenting Professors at Eckerd College]]></title><description><![CDATA[I love my alma mater, Eckerd College, and I recently discovered EC Scholar, a website that showcases faculty research profiles and publications. This seemed like a good opportunity for me to learn about QuickStatements, a Wikidata tool that particula...]]></description><link>https://blog.rayberger.org/eckerd-quickstatements</link><guid isPermaLink="true">https://blog.rayberger.org/eckerd-quickstatements</guid><category><![CDATA[QuickStatements]]></category><category><![CDATA[Wikidata]]></category><category><![CDATA[Scraping]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Fri, 27 Dec 2024 05:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1735371251920/1fe1b8a6-34d8-4464-a025-5e10ec0ae285.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I love my alma mater, <a target="_blank" href="https://www.eckerd.edu/">Eckerd College</a>, and I recently discovered <a target="_blank" href="https://ecscholar.eckerd.edu/">EC Scholar</a>, a website that showcases faculty research profiles and publications. This seemed like a good opportunity for me to learn about <a target="_blank" href="https://www.wikidata.org/wiki/Help:QuickStatements">QuickStatements</a>, a Wikidata tool that particularly interests me as I'm developing <a target="_blank" href="https://www.rayberger.org/onepagers/sites/ol-quickstatements/">something similar</a> for <a target="_blank" href="https://openlibrary.org/">OpenLibrary</a>.</p>
<p>While listening to <a target="_blank" href="https://www.libro.fm/audiobooks/9780593153895">Crying in H Mart</a>, my <a target="_blank" href="https://hackny.org/">hackNY</a> Secret Santa gift, I used a little JS to scrape ~100 faculty profiles from EC Scholar. I manually verified each profile against Wikidata and created entries for faculty members who weren't yet represented. This manual process enabled me to quickly add LinkedIn profiles and other identifiers via the <a target="_blank" href="https://www.wikidata.org/wiki/Wikidata:Tools/Wikidata_for_Web">Wikidata for Web</a> extension. Using QuickStatements, I updated each entry with the following attributes: human, gender, English speaker, Eckerd College employment (including the source URL), and a described-at URL.</p>
<p>The experience was both straightforward and enjoyable. Although I had been aware of QuickStatements for years, this was my first practical use of the tool. Essentially, I created <a target="_blank" href="https://docs.google.com/spreadsheets/d/1ZnQNGqmhf_QvXw9FZVJln4tDYGnbqNiKsKSxcvUPc3E/edit?usp=sharing">this spreadsheet</a> containing the IDs and fields I wanted to add and then pasted it into the tool. Most individuals did not yet have Wikidata entries, and I'm glad I could add them, as it should increase the discoverability of their research. It was also fun to learn more about their research interests and academic contributions.</p>
<p>Additionally, while working on this, I improved the <a target="_blank" href="https://www.wikidata.org/wiki/Property:P10999">web page title extract pattern</a> field for several properties, enabling the Wikidata for Web extension to scrape the names for more properties.</p>
]]></content:encoded></item><item><title><![CDATA[Google Gemini Can Create Past Calendar Events (Finally!)]]></title><description><![CDATA[A small but significant update caught my attention recently: Google Gemini on Android can now create calendar events in the past. This might seem like a minor feature, but I've been waiting for it for a long time.
A Trip Down Memory Lane
Back when th...]]></description><link>https://blog.rayberger.org/google-gemini-past-calendar-events</link><guid isPermaLink="true">https://blog.rayberger.org/google-gemini-past-calendar-events</guid><category><![CDATA[AI]]></category><category><![CDATA[Google]]></category><category><![CDATA[calendar]]></category><category><![CDATA[gemini]]></category><category><![CDATA[digital assistant]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Mon, 02 Dec 2024 22:53:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733179881242/e4182bea-3f5c-4733-ac67-9861128db00d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A small but significant update caught my attention recently: Google Gemini on Android can now create calendar events in the past. This might seem like a minor feature, but I've been waiting for it for a long time.</p>
<h1 id="heading-a-trip-down-memory-lane">A Trip Down Memory Lane</h1>
<p>Back when the original Google Home was released, I was excited by its promise. As a broke high school student, I hunted on Craigslist for a cheap one, eventually making an hour-long journey near Tampa with my mom to pick one up. The excitement was palpable – this was supposed to be the future.</p>
<p>However, as many of us experienced, these smart speakers didn't quite live up to their promise of getting better over time. In some ways, they regressed. Google Keep integration for shopping lists was replaced with a less capable Google Lists service. Features I hoped for, like Google Voice integration for messaging, never materialized.</p>
<h1 id="heading-the-past-event-problem">The Past Event Problem</h1>
<p>One persistent limitation across Google's voice assistants was the inability to create calendar events in the past. While this feature briefly appeared in Google Assistant on phones at some point, it wasn't consistently available. Even when Gemini launched on Android as Assistant's replacement, this functionality was absent.</p>
<p>But now, finally, Gemini can create past calendar events on Android. (see cover image)</p>
<h1 id="heading-why-create-past-events">Why Create Past Events?</h1>
<p>Years ago, I came across a yearly planning notebook filled with detailed daily schedules – meals, appointments, everything. Initially, I wondered, "Who plans their life in such detail?" But I later learned that planners often serve as historical records, not just future planning tools. People retrospectively document their activities to maintain a history of their days.</p>
<p>I've long used my calendar this way, adding events after they occur to maintain a record of my activities. It's gratifying to see Google Gemini finally supporting this use case.</p>
<h1 id="heading-looking-forward-ai-and-task-management">Looking Forward: AI and Task Management</h1>
<p>Speaking of AI assistants and task management, I'm wondering about the current state of AI integration in to-do list applications. While creating new tasks through AI/voice is relatively straightforward, I haven't encountered any apps that allow natural language commands for marking tasks complete or updating task descriptions. Given the current capabilities of AI technology, this functionality seems feasible, yet I haven't come across any to-do list applications that implement it. Let me know if you find any solutions that offer this type of AI integration.</p>
]]></content:encoded></item><item><title><![CDATA[How to Add a Brand Preset to OpenStreetMap]]></title><description><![CDATA[TLDR: Learn how to add brand presets to OpenStreetMap by following the official Name Suggestion Index contribution guide.
A video tutorial demonstrating this process is available here.
Introduction
Brand presets help OpenStreetMap editors like iD, Ev...]]></description><link>https://blog.rayberger.org/how-to-add-a-brand-preset-to-openstreetmap</link><guid isPermaLink="true">https://blog.rayberger.org/how-to-add-a-brand-preset-to-openstreetmap</guid><category><![CDATA[openstreetmap]]></category><category><![CDATA[GIS]]></category><category><![CDATA[Mapping]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[documentation]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Sun, 17 Nov 2024 19:35:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1731872092860/0efa5be0-7ab7-4625-9ee6-112f1313a7d5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>TLDR: Learn how to add brand presets to OpenStreetMap by following the official Name Suggestion Index <a target="_blank" href="https://github.com/osmlab/name-suggestion-index/blob/main/CONTRIBUTING.md">contribution guide</a>.</p>
<p>A video tutorial demonstrating this process is available <a target="_blank" href="https://youtu.be/HOrtmbWjc3o">here</a>.</p>
<h1 id="heading-introduction">Introduction</h1>
<p>Brand presets help OpenStreetMap editors like iD, Every Door, and StreetComplete maintain consistent tagging and spelling across the map. These presets appear as suggestions when adding businesses and points of interest. This guide explains how to contribute new brand presets to OpenStreetMap.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731870371899/160cdd0d-d703-48ff-9331-01d2d4cd11e7.png" alt="Brand preset example showing Warby Parker in iD editor" /></p>
<p>The image above shows a brand preset for Warby Parker appearing in the iD editor's search interface.</p>
<h1 id="heading-background-name-suggestion-index-nsi">Background: Name Suggestion Index (NSI)</h1>
<p>The Name Suggestion Index (NSI) is:</p>
<ul>
<li><p>A comprehensive database of brand information and identifiers</p>
</li>
<li><p>Connected to WikiData for retrieving logos and brand details</p>
</li>
<li><p>Separate from but used by the OpenStreetMap community</p>
</li>
<li><p>Integrated into multiple OSM editing tools</p>
</li>
</ul>
<h1 id="heading-using-nsi-guide">Using NSI Guide</h1>
<h2 id="heading-checking-existing-brands">Checking Existing Brands</h2>
<ol>
<li><p>Visit <a target="_blank" href="https://nsi.guide">NSI Guide</a></p>
</li>
<li><p>Search for your brand of interest</p>
</li>
<li><p>Review the results to see:</p>
<ul>
<li><p>WikiData information</p>
</li>
<li><p>Geographic coverage</p>
</li>
<li><p>Existing preset details</p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-regional-availability">Regional Availability</h2>
<p>Brand presets are region-specific. For example, Warby Parker presets only appear when editing in the United States since that's where their locations are.</p>
<h1 id="heading-contributing-new-brands">Contributing New Brands</h1>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ol>
<li><p>Verify Brand Notability</p>
<ul>
<li><p>The brand should have at least 30 physical locations</p>
</li>
<li><p>This requirement may be flexible in some cases</p>
</li>
<li><p>Use official sources, Wikipedia, or OSM queries.</p>
</li>
</ul>
</li>
<li><p>WikiData Entry Required</p>
<ul>
<li><p>Brand must have a <a target="_blank" href="https://www.wikidata.org/">WikiData</a> entry</p>
</li>
<li><p>You can create one if it doesn't exist</p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-how-to-contribute">How to Contribute</h2>
<p>For developers:</p>
<ul>
<li>Follow the technical guidelines in the <a target="_blank" href="https://github.com/osmlab/name-suggestion-index/blob/main/CONTRIBUTING.md">CONTRIBUTING.md</a></li>
</ul>
<p>For non-technical contributors:</p>
<ol>
<li><p>Check Existing Entries</p>
<ul>
<li><p>Search <a target="_blank" href="https://nsi.guide">NSI Guide</a></p>
</li>
<li><p>If a brand appears in NSI but not your editor:</p>
<ul>
<li><p>Verify the brand's configured regions match your editing location</p>
</li>
<li><p>Check when the brand was added - recent additions may not be in your editor yet</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p>Submit via GitHub Issues</p>
<ul>
<li><p>Go to the <a target="_blank" href="https://github.com/osmlab/name-suggestion-index/">NSI GitHub repository</a></p>
</li>
<li><p>Create a new issue including:</p>
<ul>
<li><p>Brand name</p>
</li>
<li><p><a target="_blank" href="https://www.wikidata.org/">WikiData</a> link</p>
</li>
<li><p>Number of locations</p>
</li>
<li><p>Supporting documentation (Wikipedia links, official sources)</p>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<h1 id="heading-review-and-integration-timeline">Review and Integration Timeline</h1>
<ul>
<li><p>Maintainer review typically only take a few days</p>
</li>
<li><p>Complex cases may require community discussion</p>
</li>
<li><p>Editor integration depends on their release schedule</p>
</li>
</ul>
<h1 id="heading-conclusion">Conclusion</h1>
<p>You now know how to contribute brand presets to OpenStreetMap. Whether adding new brands or updating existing ones, your contributions help improve mapping consistency for everyone.</p>
<p>PS: I created this blog post as a companion to the video tutorial, focusing on searchability rather than traditional blog formatting. After repeatedly struggling to find the Name Suggestion Index documentation, I wanted to create an easily discoverable resource for others looking to add brand presets to OpenStreetMap.</p>
<p>Update Nov 19, 2024: If you have a JSOM preset and you want to merge it into NSI someone shared a script to do so <a target="_blank" href="https://lemmy.ml/post/22613179/15052092">here</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Emergent City Review: The Messiness of Urban Development]]></title><description><![CDATA[What happens when a neighborhood wants to turn its manufacturing district green and the property owners want to turn it into retail space? This is the tension that the documentary Emergent City explores and unravels alongside the neighbors, property ...]]></description><link>https://blog.rayberger.org/emergent-city-review</link><guid isPermaLink="true">https://blog.rayberger.org/emergent-city-review</guid><category><![CDATA[Urbanism]]></category><category><![CDATA[Industrial City]]></category><category><![CDATA[brooklyn]]></category><category><![CDATA[Movies]]></category><category><![CDATA[#documentaries]]></category><category><![CDATA[movie review]]></category><category><![CDATA[New York City]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Thu, 14 Nov 2024 04:09:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1731555653841/094029fd-bffc-4bb6-95fa-920f63fcce41.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>What happens when a neighborhood wants to turn its manufacturing district green and the property owners want to turn it into retail space? This is the tension that the documentary <em>Emergent City</em> explores and unravels alongside the neighbors, property owners, and politicians.</p>
<p>It's a powerful documentary because it doesn't seek an objective truth about the situation. It merely tells the story of people as they go through the chaos.</p>
<p>A brief summary: In Brooklyn's Industry City, a large developer purchased the industrial complex and began renovating it, converting upper floors to offices. They then sought to rezone the lower floors from mandatory manufacturing use to allow retail use. This sparked intense debate in the community.</p>
<p>Many residents oppose it because they're worried:</p>
<ol>
<li><p>The good blue-collar manufacturing jobs will go away</p>
</li>
<li><p>They don't want the low-quality service jobs</p>
</li>
<li><p>It will lead to greatly increased rent costs and ultimately displacement</p>
</li>
</ol>
<p>In short, they believe the changes won't benefit the existing community.</p>
<p>The counterargument by Adrian Kimball, the CEO of Industry City, is that it's disingenuous to say local residents' children won't be able to take the new office jobs. However, this argument seems flawed since many families would be priced out of the neighborhood before their children are old enough for such positions.</p>
<p>This battle plays out over many years with <a target="_blank" href="https://www.uprose.org">UPROSE</a>, a local environmental justice organization, leading the opposition. Throughout the process, stakeholders grapple with questions about what development scenarios are possible, probable, and truly beneficial for the community rather than just the landowners.</p>
<p>As a friend put it: "the movie made democracy look messy." If you're interested in changing your city, making it greener, making it more bike-friendly, or understanding how these changes happen, this is the film for you. Check out <a target="_blank" href="https://www.emergentcitydoc.com/#screenings">the movie's website</a> for upcoming screenings. If it's not showing near you, look at <a target="_blank" href="https://www.kelly-anderson.com/">Kelly Anderson's</a> other documentaries about the city. I haven't watched any yet, but they look promising.</p>
<h1 id="heading-urbanist-musings">Urbanist musings</h1>
<ul>
<li><p>The community participation seemed to be <a target="_blank" href="https://organizingengagement.org/models/ladder-of-citizen-participation/">token</a> at best. Industry City didn't do a good job gathering feedback and seemed surprised by the questions and demands of people. Of course, the film didn't show all the meetings, but it seemed like the CEO was speaking much more than listening. If they were serious, they should have offered free childcare for these meetings (one woman in the documentary mentioned the struggle of staying late with kids at home). Given all the office space they control, you'd think they could have organized some venues that weren't overheated and overcrowded.</p>
</li>
<li><p>In urban redevelopments, one way to reduce displacement is to offer fixed rents for 10 or 20 years. Industry City couldn't implement this solution because they don't own the nearby residential properties.</p>
</li>
<li><p>The idea that manufacturing should stay to help build green infrastructure for the future is noble. But it's not clear that the manufacturing companies there would be likely to actually take on those green tasks. This reflects a form of the <a target="_blank" href="http://foodglossary.pbworks.com/w/page/86174431/Local%20Trap">local trap</a> - assuming local production is inherently better for the community.</p>
</li>
<li><p>"We are the makers and the box is empty. This is where we fill it," declares Industry City's promotional video - while they work to empty manufacturing spaces and fill them with retail.</p>
</li>
<li><p>Adrian Kimball sounds like he's full of (sh)it when he says things like they don't know how much profit could be made from rezoning and that nearby housing prices won't go up from the rezoning and development.</p>
</li>
</ul>
<p>Cover photo is a remix of <a target="_blank" href="https://commons.wikimedia.org/wiki/File:Industry_City_mural.jpg">Industry City mural.jpg</a> by Vivian Toy and <a target="_blank" href="https://commons.wikimedia.org/wiki/File:Industry_City_Dec_4,_2018_07.jpg">Industry City Dec 4, 2018 07.jpg</a> by Epicgenius. Licensed under <a target="_blank" href="https://creativecommons.org/licenses/by-sa/4.0">CC BY-SA 4.0</a></p>
]]></content:encoded></item><item><title><![CDATA[Wikidata and the 2024 Open Library Community Celebration]]></title><description><![CDATA[Every October, Open Library hosts a community celebration where volunteers showcase their work from the past year. I had the pleasure of presenting at this year's celebration and would like to share my presentation here.
On a side note, during my rec...]]></description><link>https://blog.rayberger.org/wikidata-and-the-2024-open-library-community-celebration</link><guid isPermaLink="true">https://blog.rayberger.org/wikidata-and-the-2024-open-library-community-celebration</guid><category><![CDATA[Open Source]]></category><category><![CDATA[Open Library]]></category><category><![CDATA[Wikidata]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Fri, 08 Nov 2024 18:11:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1731081412656/9ec84e48-dbeb-4511-902e-d8545227c708.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every October, <a target="_blank" href="https://openlibrary.org">Open Library</a> hosts a community celebration where volunteers showcase their work from the past year. I had the pleasure of presenting at this year's celebration and would like to share my presentation here.</p>
<p>On a side note, during my recent trip to San Francisco, I missed the <a target="_blank" href="https://blog.archive.org/2024/08/19/celebrate-with-the-internet-archive-on-october-22nd-23rd/">in-person meetup</a> at the <a target="_blank" href="https://archive.org/">Internet Archive</a> by just a day, but I still had the chance to meet the team, including Mek, Drini, Scott, and Rebecca, who graciously showed me around. Experiencing the Internet Archive firsthand was truly inspiring, and I highly recommend visiting one of their public events if you get the chance.</p>
<h1 id="heading-my-open-library-contributions">My Open Library Contributions</h1>
<p>Here is my single slide from the presentation:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731078549463/ccca57c4-2ffe-4462-955a-4f41035c07a9.png" alt="Presentation slide showing Ray Berger's photo and title as an Engineering Fellow. Titled: Wikidata &amp; Open Syllabus Project Integration. Below is a screenshot from the Internet Archive's Open Library featuring information about author Cory Doctorow. Text on the right highlights project details and a call to action for librarians, designers, and engineers." class="image--center mx-auto" /></p>
<h2 id="heading-mentoring-volunteers">Mentoring Volunteers</h2>
<p>This year, I dedicated significant energy to mentoring volunteers. Many individuals express interest in contributing to the project, and I am happy to provide guidance and respond to their questions in a timely manner, even hopping on occasional calls. I enjoy this and hope it allows the staff to focus on other important tasks (such as reviewing our pull requests 😅). I strive to foster a welcoming and accessible community, which is a rewarding experience that makes me proud to be part of the team.</p>
<h2 id="heading-open-syllabus-project-integration">Open Syllabus Project Integration</h2>
<p>I chose to highlight two projects: the Open Syllabus Project integration and the Author Wikidata integration.</p>
<p>The Open Syllabus Project provides data on how often books are referenced in syllabi, which is now available in Open Library's Solr database.</p>
<p>The integration of the Open Syllabus Project data offers opportunities to enhance search functionality by highlighting books frequently referenced in syllabi. Additionally, displaying this information on book pages can serve as a valuable indicator of a book's relevance within a particular field. While these features are not yet implemented, I hope they'll be coming in 2025.</p>
<h2 id="heading-author-wikidata-integration">Author Wikidata Integration</h2>
<p>This project has been on my back burner for years. Finally, in the past year, Drini and I worked together closely to get it out the door.</p>
<p><a target="_blank" href="https://www.wikidata.org/">Wikidata</a> is a rich, crowdsourced database of information on various topics, similar to Wikipedia (and run by the same folks). It is easy to query and has contributions from people all over the world, not just book nerds like those at Open Library.</p>
<p>Now, Open Library downloads information for any author with a Wikidata ID. This information is updated when an ID is added to an author and every 30 days thereafter to ensure the data stays fresh.</p>
<p>Currently, as shown in the screenshot of my slide with the red circle, we are only using Wikidata in one place: to display the short description of the author on their page. However, now that the data is integrated into the system, it will be straightforward to add new features.</p>
<p>Soon, we will implement language-aware Wikipedia links. If you are browsing the Spanish version of Open Library, you will see the Spanish link to Wikipedia if it exists; if not, it will display a link in another language. Additionally, I hope we will start importing authors’ photos from Wikimedia Commons. These are two relatively small enhancements that I am excited to see come to fruition. In the long term, we envision creating a map showing the origins of all the authors you have read and more.</p>
<p>Finally, I want to emphasize that this is a great example of utilizing Wikidata while also encouraging people to learn about and contribute to it. When volunteers add information about authors, they contribute to a dataset for the world, not just the Open Library website.</p>
<h1 id="heading-closing">Closing</h1>
<p>I've been contributing to Open Library for a few years now and it just keeps getting better. I'm happy I found this community and hope it inspires people to build similarly welcoming and collaborative projects. If you'd like to contribute, contact me or visit the <a target="_blank" href="https://openlibrary.org/volunteer">volunteer page</a>. Whether you're a budding librarian eager to contribute by adding <a target="_blank" href="https://docs.google.com/spreadsheets/d/1wR9szWsTMVplXxjNa3Q-s8QW_OcqeLg6fhnRiDWayCU/edit?gid=0#gid=0">missing Wikidata IDs</a>, a designer ready to enhance our author pages and search results, or an engineer interested in building visualizations and improving search functionality, we welcome your skills and passion.</p>
<p>This blog post was not written by AI, but it was transcribed by AI, as I mostly dictated it to my phone.</p>
]]></content:encoded></item><item><title><![CDATA[Using Wikidata to Generate an Open Library Collection]]></title><description><![CDATA[Back in April, I generated this Open Library collection about the Kingdom of Hawai’i.
Here’s a short blog post about how and why.
Why did I pick this topic?

I lived in Hawai'i for about a year so I have an affinity for the topic. (I also maintain a ...]]></description><link>https://blog.rayberger.org/open-library-wikidata-hawaii</link><guid isPermaLink="true">https://blog.rayberger.org/open-library-wikidata-hawaii</guid><category><![CDATA[Open Source]]></category><category><![CDATA[Open Library]]></category><category><![CDATA[Wikidata]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Ray Berger]]></dc:creator><pubDate>Tue, 21 May 2024 11:10:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716289558665/c0391143-24fe-4252-8f00-7a543fcaba89.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Back in April, I generated <a target="_blank" href="https://openlibrary.org/collections/kingdom-of-hawai'i-people">this Open Library collection</a> about the <a target="_blank" href="https://en.wikipedia.org/wiki/Hawaiian_Kingdom">Kingdom of Hawai’i</a>.</p>
<p>Here’s a short blog post about how and why.</p>
<h1 id="heading-why-did-i-pick-this-topic">Why did I pick this topic?</h1>
<ol>
<li><p>I lived in Hawai'i for about a year so I have an affinity for the topic. (I also maintain a <a target="_blank" href="https://hawaii.rayberger.org/">list of documentaries about Hawai’i</a>)</p>
</li>
<li><p>Since the kingdom no longer exists, there shouldn’t be too many updates needed for the page.</p>
</li>
<li><p>The kingdom was around recently enough that people alive then probably published some books.</p>
</li>
</ol>
<h1 id="heading-how-did-i-do-it">How did I do it?</h1>
<p>I queried Wikidata for all people with:</p>
<ol>
<li><p>An Open Library author ID</p>
</li>
<li><p>Their place of birth or country of citizenship set to the Kingdom of Hawai’i</p>
</li>
</ol>
<p>It took a bit of tinkering to get the query to work because of the place of birth can have the country encoded in different ways.</p>
<p>I used that data to generate the collection page. The whole script is <a target="_blank" href="https://gist.github.com/RayBB/9bc329cfb4f9b1833747509a1e8e04b3">here</a>.</p>
<h1 id="heading-results">Results</h1>
<p>The result are... a bit lackluster because there are only a handful of authors and most of their works don’t have covers. However, it’s a good start to see how we can use Wikidata to power more interesting pages on Open Library.</p>
<p>On the plus side, there really wasn't much information readily searchable about authors in the Kingdom of Hawai'i so maybe this will be useful to someone one day.</p>
<p>PS: <a target="_blank" href="https://github.com/internetarchive/openlibrary/pull/9130">My pull request</a> to augment the Open Library author pages with information from Wikidata was recently merged so more to come soon!</p>
]]></content:encoded></item></channel></rss>