James Reads

HomeBlog

Day of Oct 23rd, 2019

  • Susan Rice Calls Sen. Lindsey Graham 'A Piece Of S**t’ | HuffPost

    POLITICS 10/23/2019 01:08 am ET Updated 3 hours ago Former national security adviser Susan Rice aired some old grievances during a podcast set to air Wednesday, referring to Sen. Lindsey Graham (R-S.C.) as a “piece of shit” while speaking about the GOP’s endless criticism over her handling of 2012’s attack on a U.S. compound in Benghazi, Libya. Rice, who is on tour for her new book, spoke with the hosts of “Pod Save the World” this week about the Democrats’ impeachment inquiry into President Donald Trump and the Turkish invasion of Kurdish-held territory in Syria. During one fiery moment, however, former Obama administration adviser Ben Rhodes brought up the Benghazi attack to add context to Trump’s decisions surrounding foreign policy. "He's a piece of shit." — @AmbassadorRice on Lindsey Graham Hear the rest of her interview on tomorrow's #PodSaveTheWorld pic.twitter.com/9GQITwlSlg — Pod Save America (@PodSaveAmerica) October 22, 2019 “You have to understand Benghazi to understand [President] Trump,” Rhodes said during a preview of the episode posted on Twitter. “Right, because Lindsey Graham isn’t just a piece of shit now,” Tommy Vietor, another host and former Obama adviser, replied. “He’s been a piece of shit,” Rice interjected. “I said it. I said it, dammit. Finally. He’s a piece of shit.” Graham has long been one of Trump’s most ardent defenders, regularly going to bat for the president on his favorite news networks while lambasting his Democratic critics. Though he initially broke with the president’s decision to order U.S. troops to leave Syria, calling it a “national security disaster in the making,” he reversed course in an interview with Fox News this week, saying he was “increasingly optimistic” that “historic solutions” were on the horizon. Rice is not a fan of the Trump administration’s policies abroad. Earlier this month, she said the White House would have “blood” on its hands over its decision to withdraw U.S. troops from Syria and abandon the country’s Kurdish allies, effectively opening the door to a Turkish attack. “I mean, the Kurds were the pointy end of the spear who fought ISIS on our behalf,” Rice said during an interview with CNN at the time. “They bled and died because they believed in the partnership they had established with the United States, and we just threw them under the bus in 24 hours. It’s appalling. It’s disgraceful.” Just days before, she had called the decision “batshit crazy.” During the podcast, Rice also spoke about Fox News and its propensity to turn individual political figures into villains in order to inflame viewers, a standard Trump has applied to his own presidency. “You always need fresh villains,” Rice said. “Now, on Fox, many years later they just need to say my name and it’s like people start twitching. It’s like an automatic trigger point.” “Trump is the master of it,” she added. “He makes people angry every day.” The president has already attacked Rice for her criticisms this month, calling her a “disaster.” Susan Rice, who was a disaster to President Obama as National Security Advisor, is now telling us her opinion on what to do in Syria. Remember RED LINE IN THE SAND? That was Obama. Millions killed! No thanks Susan, you were a disaster. — Donald J. Trump (@realDonaldTrump) October 19, 2019 BEFORE YOU GO Source: Susan Rice Calls Sen. Lindsey Graham ‘A Piece Of S**t’ | HuffPost

    Read at 05:08 pm, Oct 23rd

  • Edit Flow Future in Flux: Here Are 5 Alternative Plugins – WordPress Tavern

    After years of unpredictable development and support, it seemed the Edit Flow plugin had finally given up the ghost last week when an Automattic support representative confirmed that it is no longer being actively developed and recommended users switch to an alternative. Nick Gernert, head of WordPress.com VIP, has since commented on our post to clarify the company’s intentions. He said Automattic is “in no way dropping support for Edit Flow:” I’ll start by saying we are in no way dropping support for Edit Flow. We do see a difference between active feature development and maintenance updates to a plugin and this post tends to use these things interchangeably. It is correct that we are not currently pushing new features for Edit Flow. However, we are committed to maintaining this and other plugins so that those who depend on them are able to continue to do so. We face the same challenge that many in software face when it comes to supporting existing work while looking to the future and where to invest energy. I hope folks can understand the delicate balance here. We accept that we have fallen short at times when it comes to maintaining our existing work and appreciate the community holding us accountable. Gernert also said the company’s VIP service is “seeing demand for WordPress in the enterprise market like never before.” The team is doubling down on its commitment to product development for this market and Gernert said outlook for Edit Flow and other Automattic plugins should improve: VIP is more committed than ever to product development for the unique needs of this space. We have recently brought on a new Head of Product and Engineering. With the addition of this role, there is a commitment to focused product development and that includes ensuring key plugins like Edit Flow are maintained. Presently, that maintenance includes security updates, critical bugs, ensuring compatibility with new versions of WordPress, and directly supporting VIP customer use. Going forward the VIP Product and Engineering teams are committed to allocating time to regularly review and address issues and provide regular updates to the plugins. As we stabilize on maintenance, new feature development will pick up in areas where we see unique opportunity. Users and developers seemed wary of this response, given the plugin’s history and more recent experiences of trying to contribute to its upkeep. James Miller, a developer who was using Edit Flow on a client project, shared his experience trying to submit a PR for a bug fix. “It doesn’t seem like it’s even being given a level of attention at the most basic level of what could be considered ‘maintained,'” Miller said. “This plugin was breaking functionality of other plugins on a client site. “I forked the repo, fixed the issue, and submitted a PR on January 26. After several months of periodic commenting and asking if anybody was even maintaining the repo, it finally got merged just last month. This doesn’t seem to me like a commitment to maintaining the plugin.” What Does this Mean for Edit Flow Users? If you’re not currently experiencing any critical bugs and you don’t require additional features beyond what it offers, Edit Flow may be still be a good option if Automattic is able to improve its maintenance. As previously predicted, any new features coming to this plugin will be those that “directly support VIP customer use.” Support for the plugin has not improved over the last week, so users may still be waiting for updates and fixes for awhile. The support forums indicate that multiple users continue to report issues with both the block editor and the classic editor, as well as conflicts with other plugins. This is likely why Automattic support representatives recommend users fork Edit Flow or switch to another solution. In support of smaller WordPress-powered publications that have an immediate need for editorial tools, we have compiled a list of alternatives that offer more frequent maintenance and support. Edit Flow’s primary features include a calendar, custom statuses, editorial comments, editorial metadata, notifications, story budget, and user groups. One of the alternatives below may be a suitable replacement, depending on which features are most important to your editorial workflow. PublishPress PublishPress is the plugin that Automattic recommended as an alternative, and it is the closest one to matching Edit Flow’s features. It has 7,000 active installs and is used by companies, non-profits, educational institutions, magazines, newspapers, and blogs. In the free plugin, PublishPress provides an editorial calendar, notifications, editorial comments, custom statuses, content overview, and the ability to create custom metadata for posts. Its creators also offer commercial add-ons for things like a content checklist, Slack notifications, multiple authors, WooCommerce checklist, and more. Since PublishPress is actually a fork of the Edit Flow plugin, users can migrate seamlessly from Edit Flow without losing any data or settings using the plugin’s built-in migration utility. The PublishPress team has also created several other publishing plugins that may also be useful for different editorial needs, including PublishPress Revisions, PressPermit, and Capability Manager Enhanced. Oasis Workflow Oasis Workflow is a plugin that allows site admins to create custom workflows for content review. It includes three process/task templates for assignment, review, and publishing actions with role-based routing. Workflows can be configured using a drag-and-drop interface. The plugin supports custom statuses, process history, task reassignment, due dates, and email reminders. Oasis Workflow is often used in healthcare, law and financial firms, universities, CPA firms, non-profits, news outlets, and other organizations that require a formal review process for publishing. A commercial version of the plugin includes features like multiple workflows, auto submit, revisions for published content, with add-ons for editorial contextual comments, teams, groups, and more. Nelio Content Nelio Content is a plugin with 6,000 active installs that includes an editorial calendar, editorial comments, tasks, and a content assistant. It also helps users schedule and automatically promote content on social networks. The plugin integrates relevant metrics from Google Analytics and social media accounts to assist users in promoting content. Editorial Calendar If the editorial calendar feature of Edit Flow is the only one you need, then the Editorial Calendar plugin might be a good alternative. It is used on more than 40,000 WordPress sites. The plugin provides an overview of when each post will be published, supports multiple authors, the ability to rearrange the schedule with drag-and-drop capabilities, and edit posts directly in the calendar. WP Scheduled Posts WP Scheduled Posts is another editorial calendar plugin that makes it easy to manage multiple authors from one place. It includes a visual calendar that can be manipulated via drag-and-drop, allowing users to easily add posts in the queue or create new posts inside the calendar. The plugin has a dashboard widget that displays post statuses for single or multiple authors. The commercial version of WP Scheduled Posts is targeted at the scheduling aspects of publishing. It offers an auto-scheduler where users can create rules to publish content automatically, as well as a missed schedule handler for automatically publishing posts that didn’t go out on schedule. Would you like to write for WP Tavern? We are always accepting guest posts from the community and are looking for new contributors. Get in touch with us and let's discuss your ideas. Like this: Like Loading... Related Source: Edit Flow Future in Flux: Here Are 5 Alternative Plugins – WordPress Tavern

    Read at 02:52 pm, Oct 23rd

  • Revealed: the 20 firms behind a third of all carbon emissions

    The Guardian today reveals the 20 fossil fuel companies whose relentless exploitation of the world’s oil, gas and coal reserves can be directly linked to more than one-third of all greenhouse gas emissions in the modern era.

    Read at 01:45 pm, Oct 23rd

  • How my iPhone landed me with a £476 fine and made me a criminal

    The digital payments revolution was meant to make things better for the consumer.

    Read at 01:37 pm, Oct 23rd

  • Trump warns GOP Senate leader Mitch McConnell about disloyal Republicans

    Even as the White House appears to settle on the legal tactics to stave off Democrats' impeachment demands, uncertainty and unease over Trump's messaging approach remains high among his Republican allies, who see the ever-growing inquiry consuming the White House.

    Read at 01:33 pm, Oct 23rd

  • George Conway and other prominent conservatives call for ‘expeditious’ impeachment probe

    More than a dozen prominent conservative lawyers, including George T. Conway III, offered their legal reasoning for an “expeditious” impeachment probe into President Trump, creating a document they hope will be read by Republicans who continue to stand by the president.

    Read at 01:29 pm, Oct 23rd

  • Trump Urged Top Aide to Help Giuliani Client Facing DOJ Charges

    President Donald Trump pressed then-Secretary of State Rex Tillerson to help persuade the Justice Department to drop a criminal case against an Iranian-Turkish gold trader who was a client of Rudy Giuliani, according to three people familiar with the 2017 meeting in the Oval Office.

    Read at 01:24 pm, Oct 23rd

  • Testing is a Gradient Spectrum

    I’m reading the n-th article where someone mentions TDD (test-driven development) as a magic word that means “doing testing” or something else and I thought I’d write down a few things as a note. There have been nearly infinity plus one articles and discussions about testing and TDD.

    Read at 01:04 pm, Oct 23rd

  • Mongoose Design Pattern: Store What You Query For

    In JavaScript, a Map is an object that stores key/value pairs. Maps are Mongoose 5.7.0 was released on September 9 and is packed with new features. One major highlight

    Read at 01:03 am, Oct 23rd

  • OpenJS Foundation welcomes first project: Node Version Manager (nvm)

    If you build Node.js applications, you may end up needing to use different versions of Node. Fortunately, there is a convenient way to install and manage different versions thanks to Node Version Manager (nvm). nvm is a POSIX-compliant bash script to manage multiple active Node.js versions.

    Read at 12:50 am, Oct 23rd

Day of Oct 22nd, 2019

  • New In PostgreSQL 12: Generated Columns

    PostgreSQL 12 comes with a new feature called generated columns. Other popular RDBMSes already support generated columns as “computed columns” or “virtual columns.” With Postgres 12, you can now use it in PostgreSQL as well. Read on to learn more.

    Read at 10:57 pm, Oct 22nd

  • Turkey begins offensive against Kurdish fighters in Syria

    AKCAKALE, Turkey (AP) — Turkey launched airstrikes, fired artillery and began a ground offensive against Kurdish fighters in northern Syria on Wednesday after U.S. troops pulled back from the area, paving the way for an assault on forces that have long been allied with the United States.

    Read at 10:55 pm, Oct 22nd

  • Turkey launches ground offensive in northern Syria

    Turkey has launched a ground offensive in northern Syria, hours after its warplanes and artillery began hitting territory held by Kurdish-led forces. President Recep Tayyip Erdogan said the operation was to create a "safe zone" cleared of Kurdish militias which will also house Syrian refugees.

    Read at 10:50 pm, Oct 22nd

  • The Structure of Solidarity Unionism: An Answer to Noam Scheiber’s “Uniting Workers, Without a Union”

    The New York Times recently published an article by Noam Scheiber praising the book Labor Law for the Rank & Filer and the organizing projects it has helped spawn.

    Read at 10:46 pm, Oct 22nd

  • ROC Confidential

    Jean-Carl Elliott describes his disillusionment after working for Restaurant Opportunities Center (ROC), and why he now believes non-profits are a dead end for worker power. From the time I was 14 years old until the time I turned 30, the only jobs I had ever held were in the food service industry.

    Read at 10:37 pm, Oct 22nd

  • The Not-Com Bubble Is Popping

    It is easy to look at today’s crop of sinking IPOs—like Uber, Lyft, and Peloton—or scuttled public offerings, like WeWork, and see an eerie resemblance to the dot-com bubble that popped in 2000.

    Read at 10:15 pm, Oct 22nd

  • How Number 10 view the state of the negotiations

    Earlier today, I sent a message to a contact in Number 10 asking them how the Brexit talks were going. They sent a long reply which I think gives a pretty clear sense of where they think things are. ‘The negotiations will probably end this week. Varadkar doesn’t want to negotiate.

    Read at 10:05 pm, Oct 22nd

  • Brexit deal approved by Commons for first time as Boris Johnson wins vote | The Independent

    MPs have approved Boris Johnson’s Brexit deal in principle, taking the country closer than ever before to leaving the European Union. The withdrawal agreement bill was given a second reading by 30 votes in a moment of triumph for the prime minister – seven months after Theresa May’s deal was thrown out for the third and final time. Some 19 Labour MPs defied Jeremy Corbyn by voting with the Conservatives to deliver the bigger-than-expected victory, along with 18 expelled Tories, including Philip Hammond and Amber Rudd. We’ll tell you what’s true. You can form your own view. From 15p €0.18 $0.18 USD 0.27 a day, more exclusives, analysis and extras. Subscribe now But the prime minister's triumph was be short-lived, as MPs rejected his attempt to ram the legislation through the Commons in just three days in another crunch vote moments later. Mr Johnson had earlier issued a dramatic threat to abandon the bill altogether and force a general election if MPs delayed Brexit until January, by forcing more debate. Read more With his hopes of leaving the EU at Halloween all but dead, Mr Johnson then announced he would "pause" the passage of the Brexit deal bill until the EU had made a decision on offering an extension. Nevertheless, the second reading for the bill is a remarkable turnaround on the situation just a few weeks’ ago, when any deal appeared impossible. Even when Mr Johnson caved into EU demands and abandoned his allies in the Democratic Unionist Party, by signing up to a customs border in the Irish Sea, he was expected to lose at Westminster. But members of the hard-line European Research Group of Tory backbenchers also switched sides and enough Labour MPs defied Mr Corbyn to help get the deal over the line. Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch Created with Sketch. Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch Created with Sketch. Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch Created with Sketch.Final Say march: Independent's best photos 1/70 Angela Christofilou/The Independent 2/70 Angela Christofilou/The Independent 3/70 Angela Christofilou/The Independent 4/70 Angela Christofilou/The Independent 5/70 Angela Christofilou/The Independent 6/70 Angela Christofilou/The Independent 7/70 Angela Christofilou/The Independent 8/70 Angela Christofilou/The Independent 9/70 Angela Christofilou/The Independent 10/70 Angela Christofilou/The Independent 11/70 Angela Christofilou/The Independent 12/70 Angela Christofilou/The Independent 13/70 Angela Christofilou/The Independent 14/70 Angela Christofilou/The Independent 15/70 Angela Christofilou/The Independent 16/70 Angela Christofilou/The Independent 17/70 Angela Christofilou/The Independent 18/70 Angela Christofilou/The Independent 19/70 Angela Christofilou/The Independent 20/70 Angela Christofilou/The Independent 21/70 Angela Christofilou/The Independent 22/70 Angela Christofilou/The Independent 23/70 Angela Christofilou/The Independent 24/70 Angela Christofilou/The Independent 25/70 Angela Christofilou/The Independent 26/70 Angela Christofilou/The Independent 27/70 Angela Christofilou/The Independent 28/70 Angela Christofilou/The Independent 29/70 Angela Christofilou/The Independent 30/70 Angela Christofilou/The Independent 31/70 Angela Christofilou/The Independent 32/70 Angela Christofilou/The Independent 33/70 Angela Christofilou/The Independent 34/70 Angela Christofilou/The Independent 35/70 Angela Christofilou/The Independent 36/70 Angela Christofilou/The Independent 37/70 Angela Christofilou/The Independent 38/70 Angela Christofilou/The Independent 39/70 Angela Christofilou/The Independent 40/70 Angela Christofilou/The Independent 41/70 Angela Christofilou/The Independent 42/70 Angela Christofilou/The Independent 43/70 Angela Christofilou/The Independent 44/70 Angela Christofilou/The Independent 45/70 Angela Christofilou/The Independent 46/70 Angela Christofilou/The Independent 47/70 Angela Christofilou/The Independent 48/70 Angela Christofilou/The Independent 49/70 Angela Christofilou/The Independent 50/70 Angela Christofilou/The Independent 51/70 Angela Christofilou/The Independent 52/70 Angela Christofilou/The Independent 53/70 Angela Christofilou/The Independent 54/70 Angela Christofilou/The Independent 55/70 Angela Christofilou/The Independent 56/70 Angela Christofilou/The Independent 57/70 Angela Christofilou/The Independent 58/70 Angela Christofilou/The Independent 59/70 Angela Christofilou/The Independent 60/70 Angela Christofilou/The Independent 61/70 Angela Christofilou/The Independent 62/70 Angela Christofilou/The Independent 63/70 Angela Christofilou/The Independent 64/70 Angela Christofilou/The Independent 65/70 Angela Christofilou/The Independent 66/70 Angela Christofilou/The Independent 67/70 Angela Christofilou/The Independent 68/70 Angela Christofilou/The Independent 69/70 Angela Christofilou/The Independent 70/70 Angela Christofilou/The Independent 1/70 Angela Christofilou/The Independent 2/70 Angela Christofilou/The Independent 3/70 Angela Christofilou/The Independent 4/70 Angela Christofilou/The Independent 5/70 Angela Christofilou/The Independent 6/70 Angela Christofilou/The Independent 7/70 Angela Christofilou/The Independent 8/70 Angela Christofilou/The Independent 9/70 Angela Christofilou/The Independent 10/70 Angela Christofilou/The Independent 11/70 Angela Christofilou/The Independent 12/70 Angela Christofilou/The Independent 13/70 Angela Christofilou/The Independent 14/70 Angela Christofilou/The Independent 15/70 Angela Christofilou/The Independent 16/70 Angela Christofilou/The Independent 17/70 Angela Christofilou/The Independent 18/70 Angela Christofilou/The Independent 19/70 Angela Christofilou/The Independent 20/70 Angela Christofilou/The Independent 21/70 Angela Christofilou/The Independent 22/70 Angela Christofilou/The Independent 23/70 Angela Christofilou/The Independent 24/70 Angela Christofilou/The Independent 25/70 Angela Christofilou/The Independent 26/70 Angela Christofilou/The Independent 27/70 Angela Christofilou/The Independent 28/70 Angela Christofilou/The Independent 29/70 Angela Christofilou/The Independent 30/70 Angela Christofilou/The Independent 31/70 Angela Christofilou/The Independent 32/70 Angela Christofilou/The Independent 33/70 Angela Christofilou/The Independent 34/70 Angela Christofilou/The Independent 35/70 Angela Christofilou/The Independent 36/70 Angela Christofilou/The Independent 37/70 Angela Christofilou/The Independent 38/70 Angela Christofilou/The Independent 39/70 Angela Christofilou/The Independent 40/70 Angela Christofilou/The Independent 41/70 Angela Christofilou/The Independent 42/70 Angela Christofilou/The Independent 43/70 Angela Christofilou/The Independent 44/70 Angela Christofilou/The Independent 45/70 Angela Christofilou/The Independent 46/70 Angela Christofilou/The Independent 47/70 Angela Christofilou/The Independent 48/70 Angela Christofilou/The Independent 49/70 Angela Christofilou/The Independent 50/70 Angela Christofilou/The Independent 51/70 Angela Christofilou/The Independent 52/70 Angela Christofilou/The Independent 53/70 Angela Christofilou/The Independent 54/70 Angela Christofilou/The Independent 55/70 Angela Christofilou/The Independent 56/70 Angela Christofilou/The Independent 57/70 Angela Christofilou/The Independent 58/70 Angela Christofilou/The Independent 59/70 Angela Christofilou/The Independent 60/70 Angela Christofilou/The Independent 61/70 Angela Christofilou/The Independent 62/70 Angela Christofilou/The Independent 63/70 Angela Christofilou/The Independent 64/70 Angela Christofilou/The Independent 65/70 Angela Christofilou/The Independent 66/70 Angela Christofilou/The Independent 67/70 Angela Christofilou/The Independent 68/70 Angela Christofilou/The Independent 69/70 Angela Christofilou/The Independent 70/70 Angela Christofilou/The Independent The so-called programme motion - setting out his planned timetable - was defeated by 322 votes to 308, prompting the government to shelve it and announce its intention to bring back debate on the Queen's Speech instead. perform eplayer Mr Johnson told MPs: "I will speak to EU member states about their intentions. Until they have reached a decision we will pause this legislation." He added: "Let me be clear. Our policy remains that we should not delay, that we should leave the EU on October 31 and that is what I will say to the EU and I will report back to the House. "And one way or another we will leave the EU with this deal, to which this House has just given its assent." Source: Brexit deal approved by Commons for first time as Boris Johnson wins vote | The Independent

    Read at 08:46 pm, Oct 22nd

  • Brexit: Deal essentially impossible, No 10 source says after PM-Merkel call

    Boris Johnson and the German chancellor spoke earlier about the proposals he put forward to the EU - but the source said she made clear a deal based on them was "overwhelmingly unlikely". The BBC's Adam Fleming said there was "scepticism" within the EU that Mrs Merkel would have used such language.

    Read at 06:09 pm, Oct 22nd

  • The Humiliation of Lindsey Graham

    He traded his honor to be “relevant” as a Trump adviser. But on Syria, Trump didn’t bother to ask what he thought. It was a cri de cœur from Lindsey Graham, the lament of the sycophant scorned.

    Read at 06:04 pm, Oct 22nd

  • Exclusive: Official Who Heard Call Says Trump Got 'Rolled' By Turkey and 'Has No Spine'

    Donald Trump got "rolled" by Turkish President Recep Tayyip Erdogan, a National Security Council source with direct knowledge of the discussions told Newsweek. In a scheduled phone call on Sunday afternoon between President Trump and President Erdogan, Trump said he would withdraw U.S.

    Read at 06:01 pm, Oct 22nd

  • House Democrats consider masking identity of whistleblower from Trump’s GOP allies in Congress

    October 7, 2019 at 10:58 PM EDTHouse Democrats are weighing extraordinary steps to secure testimony from a whistleblower whose complaint prompted their impeachment inquiry, masking his identity to prevent President Trump’s congressional allies from exposing the individual, according to three offic

    Read at 05:52 pm, Oct 22nd

  • GNU Project Maintainers Move to Oust Richard Stallman from Leadership

    GNU Project maintainers are working to oust Richard Stallman from his position as head of the organization. In a joint statement published yesterday morning, a collection of 22 GNU maintainers and developers thanked Stallman for his work and declared that he can no longer represent the project:

    Read at 05:42 pm, Oct 22nd

  • Cascading Cache Invalidation — Philip Walton

    Cascading Cache Invalidation October 9, 2019 For several years now, pretty much every article published on caching best practices has recommended the following two things for deploying JavaScript code in production: Add revision information to the filenames of your assets (usually content hashes) Set far-future expiry or max-age caching headers (so they don’t have to be revalidated for returning visitors) Every bundling tool I know of supports adding content hashes with a simple configuration rule (like the following), and as a result this practice has now become ubiquitous: The other thing most performance experts recommend is code splitting, which allows you to split your JavaScript code into several separate bundles that can be loaded in parallel or even on demand. Specifically in regards to caching best practices, one of the many claimed benefits of code splitting is that changes made to a single source file won’t invalidate the entire bundle. In other words, if a security fix is released for a single npm dependency, and you’re code-splitting all your node_module dependencies into a separate “vendor” chunk, then only your vendor chunk should have to change. The problem is, when you combine all of these things, it rarely results in effective long-term caching. In practice, changes to one of your source files almost always invalidates more than one of your output files—and this happens because you’ve added revision hashes to your filenames. The problem with revisioning filenames Imagine you’ve built and deployed a website, and you’ve used code splitting so most of your JavaScript is loaded on demand. In the following dependency graph, there’s a main entry chunk, three asynchronously-loaded dependency chunks (dep1, dep2 and dep3), and then a vendor chunk that includes all the site’s node_module dependencies. And in following with caching best practices, all filenames include revision hashes. A typical JavaScript module dependency tree Since the dep2 and dep3 chunks import modules from the vendor chunk, at the top of their generated output code you’ll likely see import statements that looks like this: Now consider what happens if the contents of the vendor chunk change. If the contents of the vendor chunk change, then the hash in the filename will have to change as well. And since the vendor chunk’s filename is referenced in import statements in both the dep2 and dep3 chunks, then those import statements will have to change too: But since those import statements are part of the contents of the dep2 and dep3 chunks, changing them means their content hash will change, and thus their filenames must also change. But it doesn’t stop there. Since the main chunk imports the dep2 and dep3 chunks and their filenames have changed, the import statements in main will have to change too: Finally, since the contents of main have changed, its filename must change along with the others. After all these changes, here’s what the new graph looks like: The modules in the tree affected by a single code change in a leaf node As you can see from this example, a tiny code change made to just one source file actually invalidated 80% of the chunks in the bundle. While it’s true that not all changes will be this bad (e.g. leaf node invalidations cascade up to the root nodes, but root node invalidations don’t cascade down to the leaf nodes), in an ideal world, you wouldn’t have any unnecessary cache invalidations. So this raises the question: Is it possible to get the benefits of immutable assets and long term caching without cascading cache invalidations? Solutions to the problem The problem with content hashes in filenames is not technically that the hashes appear in the filenames, the problem is that those hashes then appear in the contents of other files, which causes those files to be invalidated when those hashes change. So the solution to this problem is to make it possible for the code in dep2 and dep3 to import the vendor chunk without specifying any version information, and at the same time guarantee that the version loaded is the correct version, given the current versions of dep2 and dep3. As it turns out, there are a number of techniques you can use to accomplish this: Import Maps Service worker Custom script-based loaders Technique 1: Import Maps Import Maps is the simplest and easiest-to-implement solution to the cascading invalidation problem, but unfortunately it’s not currently supported in any browser except Chrome (behind a flag). But I want to show you this option first because I do think it’ll be the solution most people use in the future, and it also helps explain all the other solutions I want to present. To use Import Maps to prevent cascading cache invalidation, you have to do three things: 1) configure your bundler to NOT include revision hashes in the filenames and generate your bundle. Given the module graph in the previous example, when bundled without content hashes, the files in the output directory should look like this: dep1.mjs dep2.mjs dep3.mjs main.mjs vendor.mjs And the import statements in those modules will also not include revision hashes: 2) Use a tool like rev-hash to generate a copy of each file with a revision string added to the filename. Once you’ve done this, here’s how the contents of your output directory should look (notice there are two versions of each file): dep1-b2c3.mjs", dep1.mjs dep2-3c4d.mjs", dep2.mjs dep3-d4e5.mjs", dep3.mjs main-1a2b.mjs", main.mjs vendor-5e6f.mjs", vendor.mjs 3) Create a JSON object mapping each un-revisioned URL to the revisioned URL and add it to your HTML templates This JSON object is the import map, and it will look something like this: { : { : , : , : , : , : , } } Now, whenever your browser sees an import statement for a URL matching one of the import map keys, it will instead load the URL specified by that key’s value. Using the above as an example, import statements referencing /vendor.mjs will actually request and load /vendor-5e6f.mjs. This means the source code of your modules can safely reference the un-revisioned module names and the browser will always load the revisioned files. And since the revision hashes don’t appear in the module’s source code (they only appear in the import map) changes to those hashes won’t ever invalidate any modules other than the one that changed. Note: you might be wondering why I made a copy of each file instead of just renaming it. This is necessary in order to support browsers that don’t support Import Maps. Those browsers will see a request for /vendor.mjs and just load that URL as normal, so both files need to exist on your server. If you want to see Import Maps in action, I’ve created a set of demos showcasing all of the techniques outlined in this article. Also check out the build configuration if you want to see how I generate the import map and revision hashes for each file. Technique 2: service worker The second option to solve this problem is to replicate the functionality of Import Maps in a service worker. For example, with service worker you can listen for fetch events with request URLs that match the keys of an import map and then respond to those events by actually requesting the revisioned version of each URL: But given that this is service worker code, it will only take effect after the service worker has installed and activated—which means the un-revisioned files will be requested on the first load, and the revisioned files will be requested every load thereafter. In other words, two downloads for the same file. Given this, it may seem like service worker is not a viable solution to cascading cache invalidation. But if you’ll allow me to challenge long-standing caching best practices for just a moment, consider what would happen if you stopped using content hashes in your filenames, and instead you put them in your service worker logic. This is actually what tools like Workbox do when precaching assets—they generate a hash of the contents of each file in your build and they store that mapping in the service worker (think of it like an internal import map). They also cache the resources for you when the service worker first installs, and they automatically add fetch listeners to respond to matching requests with the cached files. While serving un-revisioned assets from your server may seem scary (and counter to everything you’ve been taught), the request for these assets is only made when your service worker is first installed. Future requests for these resources go through the Cache Storage API (which doesn’t use caching headers), and the only time new requests are made to your server is when a new version of your service worker is deployed (and you want a fresh version of those files anyway). So as long as you don’t deploy new versions of your modules without also updating your service worker (which is definitely not recommended), you’ll never have version conflicts or mismatches. To precache files with workbox-precaching, you can pass a list of asset URLs along with their corresponding revision strings to its precacheAndRoute() method: How you generate the revision strings for each asset is up to you, but if you don’t want to generate them yourself, the workbox-build, workbox-cli, and workbox-webpack-plugin packages make it easy to generate the precache manifest for you (they can even generate your entire service worker). My demo app includes examples of using service worker precaching in both a Rollup app (via workbox-cli) and a webpack app (via workbox-webpack-plugin). Technique 3: using a custom script-based loader If you’re unable to use either Import Maps or service worker on your site, a third option is to implement the functionality of Import Maps via a custom script loader. If you’re familiar with AMD-style module loaders (like SystemJS or RequireJS), you’re probably aware that these module loaders typically support defining module aliases. In fact, SystemJS actually supports aliases using Import Map syntax, so it’s really easy to solve this caching problem in a very future-friendly way (that also works in all browsers today). If you’re using Rollup, you can set the output format option to system, and in that case creating the import map for your app is exactly the same as the process I described in technique #1. My demo app includes an example site that uses Rollup to bundle to SystemJS and an import map to load hashed versions of files. What about webpack? Wepack is a custom script loader as well, but unlike classic AMD-style loaders, the loader that webpack generates is actually tailor made to each specific bundle it produces. The advantage of this approach is that the webpack runtime can (and does) include its own mapping between chunk names/IDs and their URLs (similar to what I recommend in this article), which means code-split, webpack bundles are less likely to experience cascading cache invalidations. So the good news for webpack users is, as long as you’ve configured your webpack build correctly (splitting out your runtime chunk, as described in webpack’s caching guide), then changes to a single source module shouldn’t ever invalidate more than two chunks (the chunk containing the changed module and the runtime chunk). The bad news for webpack users is that webpack’s internal mapping is non-standard, so it can’t integrate with any other tools, and you also can’t customize how the mapping is made. For example, you can’t hash webpack’s output files yourself (as described in technique #1 above) and put your own hashes in the mapping. And this is unfortunate because the content hashes webpack uses are not actually based on the contents of the output files, they’re based on the contents of the source files and build configurations, which can lead to subtle and hard to catch bugs (#1315, #1479, #7787). If you’re using webpack to build an app that also uses service worker, I’d recommend using workbox-webpack-plugin and the caching strategy I described in the previous section. The plugin will generate revision hashes based on the contents of webpack’s output chunks, which means you don’t have to worry about these bugs. Plus, not dealing with hashed filenames is generally simpler than dealing with hashed filenames. What about other assets? I’ve shown how referencing revisioned filenames in your JavaScript code can lead to cascading cache invalidation, but this problem can apply to other assets as well. For example, both CSS and SVG files often reference other assets (e.g. images) that may have revisioned filenames. As with JavaScript files, you can use either Import Maps or service workers to work around the cascading invalidation problem. For assets like images or video, this is not a problem at all, so all the existing best practices still apply. The key point to remember is that any time file A loads file B and also includes file B’s revision hash in its source code, then invalidating file B will also invalidate file A. For all other assets, you can ignore the advice in this article. Final thoughts Hopefully this article has encouraged you to investigate whether your site might be affected by cascading cache invalidations. An easy way to test is to build your site, change a single line of code in a file lots of modules import, and then rebuild your site. If the filename of more than one file changes in your build directory, then you’re seeing cascading cache invalidation, and you might want to consider adopting one of the techniques I discussed above. As for which technique to pick, honestly, it depends. When Import Maps ships in browsers, it’ll be the easiest and most comprehensive solution. But until that happens, it clearly doesn’t make sense. If you’re currently using service workers—especially if you’re currently using Workbox—then I’d recommend technique #2. Service worker precaching is how I currently solve the problem on this site, and service worker is the only option if you also deploy native JavaScript modules. (And given that 98% of my users have browsers that support service worker and native modules, it was an easy decision). If service worker is not an option, then I’d recommend technique #3 with SystemJS, as it’s the most future-friendly of the custom script-loader options available today, and it has a clear upgrade path to using Import Maps in the future. As with any performance advice, your mileage may vary. it’s important to measure and understand whether something is actually a problem before fixing it. If your release cycle is infrequent and your changesets are typically large, then cascading invalidation is probably not a concern for you. On the other hand, if you frequently deploy small changes, then your returning users may be downloading a lot of code they already have in their cache. And fixing that could significantly improve page load performance. Source: Cascading Cache Invalidation — Philip Walton

    Read at 05:19 pm, Oct 22nd

  • Why Congress Might Impeach Trump and Actually Remove Him

    Photo: Saul Loeb/AFP/Getty Images If you have seen the 1995 movie Casino, the fate of Joe Pesci’s character gives a fairly good sense of how Donald Trump might eventually be impeached and removed from office. If you haven’t seen Casino, Pesci’s character, Nicky Santoro, is basically the same as his GoodFellas character: a mobster so violent and erratic he scares the other mobsters. Throughout the film, the narrator tells us that the Mafia bosses disapprove of Santoro’s out-of-control behavior but let him operate anyway because he keeps sending suitcases full of cash back home every month. Their tolerance appears to be infinite, until eventually they reach a breaking point and bury him in a cornfield. Throughout Trump’s presidency, I’ve dismissed the possibility that he could be removed from office. In all likelihood, the Senate will come nowhere close to mustering the 67 votes needed to do so. But over the past few weeks, the outline of a removal scenario has begun to take shape. The prospect is no longer a fantasy. The Republican Establishment has largely submitted to Trump, and its acquiescence has come to seem like an immutable fact of this partisan age. But the alliance between Trump and the Republican Congress has visibly fragmented in recent weeks. Last week, the House voted 354-60 to condemn Trump’s Syria policy. Mitch McConnell has promised an even stronger resolution of disapproval in the Senate. Internal pressure from Republicans forced Trump to reverse his plans to hold the G7 summit at a Trump property, a crushing defeat for a president who despises both outward signs of weakness and missed chances to profit. Senate Republicans may both fear Trump and use him for their own ends, but they have very little love for him. Almost all of them would privately vote for an act-of-God scenario where Trump drops dead — not violently but peacefully, without suffering, ideally surrounded in his bed by a loving retinue of Fox News personalities, Ivanka, and perhaps a tasteful array of magazine covers bearing his likeness. The trouble for the Republican Senate has always been how they get from here to there. The near-certainty that the House will vote to impeach Trump this year sets in motion events that could lead to removal. Initially, many analysts predicted the Republican Senate would either do nothing in the face of an impeachment vote or hold a perfunctory vote to dismiss the charges. But McConnell has, somewhat surprisingly, announced his intention to hold a real trial. Either McConnell takes the charges against Trump seriously or, more likely, his hand has been forced by a small number of vulnerable or dissident members who are demanding serious proceedings. Whatever the explanation, McConnell is not going to simply ignore impeachment like it’s a Merrick Garland nomination. As of now, Mitt Romney is the only Republican senator making a case for conviction. But his colleagues are mostly refraining from defending Trump’s behavior outright or echoing his ever-shifting lines of defense. McConnell has blasted the House investigation as a partisan process. “I don’t think many of us were expecting to witness a clinic in terms of fairness or due process,” he complained, “but even by their own partisan standards, House Democrats have already found new ways to lower the bar.” Yet for all his typical disingenuous smarm, denouncing the process in the other chamber is much weaker brew than defending Trump’s conduct, which he has largely failed to do. Indeed, McConnell’s argument opens the door to finding guilt through a “fair” process McConnell runs. Even the sycophantic Lindsey Graham left the door open more than a crack when asked by Jonathan Swan if he could imagine voting to convict. “Sure. I mean … show me something that … is a crime,” he said. “If you could show me that, you know, Trump actually was engaging in a quid pro quo, outside the phone call, that would be very disturbing.” A “very well-connected Republican in Washington” estimated to Chris Wallace that there is a 20 percent chance the Senate votes for removal. And what if it did? The power dynamic between Trump and Senate Republicans is oddly asymmetrical. Trump has the power to end the career of dissidents, and he has flaunted it, forcing once-safe figures like Jeff Flake and Bob Corker to retire when they defied him. But his power lies only in the ability to pick off heretics one by one. The Senate Republicans can band together to vote him out, and Trump would have little recourse. Trump would, to be sure, rage furiously against a party that betrayed him and try to whip up his followers against them in 2020, perhaps even running an independent campaign. But his power relies on the support of the conservative media apparatus, which is loyal to the Republican Party. Fox News fell behind Trump because his interests dovetailed with those of the GOP as a whole. If the two began to work at cross-purposes, it would likely turn on him as rapidly as it fell in line after he won the nomination. The cult of personality around Trump is a creation of the party-controlled media. To assume Republican voters would remain loyal to a Trump who has turned against the party extends them too much credit. They will follow whomever they are told to follow. If that leader is Mike Pence, they will learn to adore his steadfast qualities of leadership, steely devotion to the timeless principles of Reaganism, and weird shoulder fetishism. To be sure, throwing out Trump entails a lot of risk. To date, Republicans have taken the safer course of sticking with him despite all his counterproductively repellent behavior. To outsiders, their alliance with Trump appears immutable. But on the inside, the picture may be more fluid. The Republican Establishment took great comfort in the presence of John Kelly, James Mattis, H. R. McMaster, and other staid figures who quietly assured official Washington they could restrain the president’s destructive impulses. Their departure has given Trump a freer hand to seize the powers of his office and act out in ways that evade any means of control. The Syria debacle is genuinely alarming to the party, because it shows Trump unleashing a strategic catastrophe, leading to thousands of escaped terrorists, through a simple phone call the implications of which he seems not to have understood. The up-front costs of ripping off the Band-Aid and removing Trump might seem less risky than allowing another year of a completely unconstrained toddler president. In Casino, the bosses accepted a lot of erratic and risky behavior from Nicky Santoro because he was ultimately a useful ally. They didn’t care that he was a violent criminal — they were violent criminals, too. But they eventually decided that his flamboyant and uncontrollable behavior put their whole racket at risk. And when their calculation of his value tipped from acceptable risk to unacceptable risk, the end came swiftly and unexpectedly. The Republican Establishment doesn’t have hit men, but it does have a constitutional process at its disposal that is being helpfully initiated by House Democrats. That its members would band together to cast out a president adored by their party’s base seemed until recently unthinkable. Now it is not. Get the latest from Jonathan Chait in your inbox. Analysis and commentary on the latest political news from New York columnist Jonathan Chait. Terms & Privacy Notice By submitting your email, you agree to our Terms and Privacy Notice and to receive email correspondence from us. Why Congress Might Impeach Trump and Actually Remove Him Source: Why Congress Might Impeach Trump and Actually Remove Him

    Read at 02:31 pm, Oct 22nd

  • U.S. Supreme Court Denies Domino’s Appeal to Determine Whether Websites Must Be Accessible

    In what is seen as a win for accessibility advocates, the U.S. Supreme Court denied Domino’s petition to appeal a lower-court decision on whether the pizza chain’s website and mobile app must be accessible to those with disabilities. The earlier U.S.

    Read at 02:12 pm, Oct 22nd

  • President Endorses Turkish Military Operation in Syria, Shifting U.S. Policy

    WASHINGTON — In a major shift in United States military policy in Syria, the White House said on Sunday that President Trump had given his endorsement for a Turkish military operation that would sweep away American-backed Kurdish forces near the border in Syria.

    Read at 01:46 pm, Oct 22nd

  • US to let Turkish forces move into Syria, abandoning Kurdish allies

    The White House has given the green light to a Turkish offensive into northern Syria, moving US forces out of the area in an abrupt foreign policy change that will in effect abandon Washington’s long-time allies, the Kurds.

    Read at 01:38 pm, Oct 22nd

  • The Rich Really Do Pay Lower Taxes Than You

    Almost a decade ago, Warren Buffett made a claim that would become famous. He said that he paid a lower tax rate than his secretary, thanks to the many loopholes and deductions that benefit the wealthy. His claim sparked a debate about the fairness of the tax system.

    Read at 01:36 pm, Oct 22nd

  • Scoop: Trump's private concerns of an impeachment legacy

    President Trump has told friends and allies he worries about the stain impeachment will leave on his legacy. Driving the news: In a phone call with House Republicans on Friday, Trump articulated why he really doesn't want this.

    Read at 01:32 pm, Oct 22nd

  • Trump Won’t Stop His Abuses of Power

    One could be forgiven for thinking that President Donald Trump wants to be impeached. On Thursday, on live television in front of the White House, Trump reiterated his call for Ukraine to investigate former Vice President Joe Biden, his leading rival in the 2020 election.

    Read at 01:30 pm, Oct 22nd

  • ExpressionEngine Under New Ownership, Will Remain Open Source for Now

    EllisLab founder Rick Ellis announced yesterday that ExpressionEngine has been acquired by Packet Tide, the parent company of EEHarbor, one of the most successful EE add-on providers and development agencies in the community.

    Read at 01:26 pm, Oct 22nd

  • Blocking Third-Party Hands from the Cookie Jar

    Brave: Blocks by default Safari: "Intelligent Tracking Prevention" is more complicated, but it does block third-party cookies by default (February 21, 2019). Firefox: Blocks by default Edge: You can enable tracking prevention and pick a level.

    Read at 01:23 pm, Oct 22nd

  • Trump’s defiance of oversight presents new challenge to Congress’s ability to rein in the executive branch

    For the first time since Democrats took control of the House last year, President Trump’s effort to stonewall congressional efforts at oversight have begun to show some cracks.

    Read at 01:21 pm, Oct 22nd

  • NYC Democratic Socialists’ new AOC could be this rising star from Brooklyn

    Jabari Brisport could be the next AOC. Leaders of New York City’s Democratic Socialists, energized by the rise of Rep. Alexandria Ocasio-Cortez, are buzzing over the 32-year-old Brooklynite, saying he looks like a new star of the party.

    Read at 12:47 pm, Oct 22nd

  • Tenant organizer challenging incumbent doesn’t think “anyone should be paying rent”

    A tenant organizer backed by the socialist group that helped propel Alexandria Ocasio-Cortez into Congress is targeting a Brooklyn legislator — and landlords.

    Read at 12:42 pm, Oct 22nd

Day of Oct 21st, 2019

  • Goodbye Security Deposit, Hello... Monthly Insurance Payment?

    In what is beginning to seem like a new world order for New York City renters, a crop of start-ups are seeking to do away with security deposits by offering to insure apartments for landlords and charging tenants a small monthly fee in return.

    Read at 10:59 pm, Oct 21st

  • 'It Is A Racist System': NYC Advocates, Lawmakers Demand Greater Oversight At Child Welfare Agency

    Child abuse investigations conducted by the Administration for Children’s Services are rife with racial and economic disparities, according to advocates and lawmakers calling for a series of reforms at the agency.

    Read at 10:53 pm, Oct 21st

  • Trump claims he's the victim of 'phony emoluments clause'

    The president was defensive of his initial decision, asserting that hosting the G-7 summit at the Trump National Doral Miami resort would have saved taxpayer money because he “would have done it for free.

    Read at 10:51 pm, Oct 21st

  • Foreign Correspondent: What is the U.S. Role in the Hong Kong Protests?

    I first met Jason Lee when he was promoting jazz concerts in his hometown of Hong Kong. More recently, he has been sending me Facebook messages about the Hong Kong protests.

    Read at 10:51 pm, Oct 21st

  • Between Washington and Beijing

    The conventional wisdom about the Hong Kong protests is wrong. The threat to Hong Kong's already-weak democracy comes not only from China’s authoritarian state capitalism but the West’s neoliberalism. It has become impossible for American elites to ignore the Hong Kong protests.

    Read at 10:48 pm, Oct 21st

  • How Should Americans Respond to the Hong Kong Protests?

    With news pouring in of demonstrations in Hong Kong, people in the United States are wondering what is going on there and whether we should support the movement. The U.S.

    Read at 10:38 pm, Oct 21st

  • The Window for Brexit May Already Have Closed

    The British people have changed their mind about Brexit. Beginning in the summer of 2017, and accelerating in the summer of 2018 by an ever wider margin, British people have told pollsters that they voted wrong in the Brexit referendum of June 2016.

    Read at 10:33 pm, Oct 21st

  • Inside the Last, Desperate Days of de Blasio’s 2020 Campaign

    “We both knew that this was a Don Quixote mission,” said one donor who was solicited by Mayor Bill de Blasio as the end drew near.As the end of his presidential bid beckoned last month, Mayor Bill de Blasio furiously worked the phones, trying to forestall the inevitable.

    Read at 10:30 pm, Oct 21st

  • Python at Scale: Strict Modules

    Welcome to the third post in our series on Python at scale at Instagram! As we mentioned in the first post in the series, Instagram Server is a several-million-line Python monolith, and it moves quickly: hundreds of commits each day, deployed to production every few minutes.

    Read at 10:21 pm, Oct 21st

  • Asterisks in Python: what they are and how to use them

    There are a lot of places you’ll see * and ** used in Python. These two operators can be a bit mysterious at times, both for brand new programmers and for folks moving from many other programming languages which may not have completely equivalent operators.

    Read at 10:12 pm, Oct 21st

  • “It’s Very Unethical”: Audio Shows Hospital Kept Vegetative Patient on Life Support to Boost Survival Rates

    ProPublica is a nonprofit newsroom that investigates abuses of power. Sign up for ProPublica’s Big Story newsletter to receive stories like this one in your inbox as soon as they are published. This story was co-published with NJ Advance Media and WNYC.

    Read at 10:01 pm, Oct 21st

  • Aftermath – tyler.io

    So last week was incredibly stupid. Broken is #1 on Hacker News I’ve had one thing I’ve written reach the #1 spot on Hacker News before, plus an old website that reached the top of the trending del.icio.us charts, as well as a few moronic tweets that garnered a couple hundred likes, and one appearance on Slashdot back in the day. But the last seven days were my first real taste of how awful it must be to actually be, you know, internet famous. So, because all this was new to me and I’m a data geek, I’d thought I’d share some final numbers. And if you’re a HN visitor, make sure you stick around for a fun surprise. But first… Let me say this. I’m often highly critical of Apple. Because I believe in their vision of computing, and also because my livelihood depends on their ecosystem remaining healthy. I’ve written many times on this blog about dumb bugs I’ve encountered on macOS and iOS. Even going so far as to refer to Photos.app as “fuckery”. And I’m no stranger to writing pithy, negative, reactionary tweets, either. But here’s the thing. My big post from last week? The one you’re probably aware of if you’re reading this site right now? It wasn’t fair. Don’t get me wrong. I fully stand behind every criticism I leveled at Apple. From the specific bugs, to the broader statements about detecting a lack of focus on the Mac in recent years, to my final thesis about their lock-step, annual release cycle hurting the company’s ability to maintain software quality. But the part that wasn’t fair. The parts that I regret are my direct insults at those in charge. I’m all for eating the rich and all that, but in a general, class-warfare sense. When I’ve written mean things about Apple previously, I kept my words pointed at the company as a whole. Last week I took aim at a specific, small group of people. I forgot that for many of them, the Mac has been their life’s work. I’ve attended WWDC eleven times and had the opportunity to talk with quite a few Apple executives. They were great. And I don’t think they were just gaslighting me either. So I shouldn’t attack them for putting so much effort into a product that I also love – even when I vehemently disagree with them at times. Some folks inside the company reminded me of that in the past few days and called me out on my shit. And so, I’m sorry. I’ll do better. Here’s what you came here for… I guess I’ll go through this in the order it happened. After a ridiculously stupid first fifteen minutes on Catalina, I posted this dumb screenshot and tweet as I was walking out the door to dinner. I thought I was exceedingly clever with my phrasing. Turns out, the internet did, too. By the time we got our drinks, my phone was buzzing every five to ten seconds with another like or retweet. A little while later it had ramped to a twitter notification (literally) every second. And by the time dinner was over, those notifications stopped saying “@SoAndSo liked your tweet”. Instead, they changed to “15 people liked your tweet”. Every. Second. At first it was fun. But by the time I was sitting in my kids’ room that night waiting for them to fall asleep, I realized my phone was simply unusable because the interruptions never stopped. Even after I turned off alerts, it was impossible to wade through all the noise in my mentions to find legit comments from people I was interested in. I’ve never used Twitter’s “quality” feature before to filter my notifications, because I’m not popular enough. But now I realize that feature is absolutely necessary for high-profile folks to actually use the platform. Here are the final engagements numbers, according to Twitter, seven days later: Tweet Engagement Statistics And here’s a fun Twitter visualization. It’s me scrolling as fast as I can through my activity timeline over the past four days – Twitter won’t let me go back the full seven since all this started. Scrolling Twitter activity timeline And when watching that, keep in mind that the vast majority of those table cells are notifications like “12 people retweeted you”, not just a single “@timapple liked your tweet”. So that tweet blew up on Monday. On Wednesday night I got the bright idea to write a lengthy followup about the Catalina bugs I ran into and my thoughts on the state of the Mac in general. 2,500 words over an hour or so. Do a quick check for spelling mistakes. Publish. Go to sleep. Around 11am the next day, I received a Twitter DM from an Apple engineering manager who (I thought) had somehow stumbled across my post. And they very kindly asked if I could jump on a call to go into more details about my post and the bugs I identified. They were super awesome and I’m looking forward to working with them more this week. But I did find it odd that it hit their radar (no pun intended) so quickly and that they would be so immediately proactive about reaching out. And that’s when, just a few minutes later, Vigil texted me that my web server had fallen over. Both this blog and my company website were inaccessible. And so were all the other sites I host on this machine. I couldn’t even SSH in. I logged into Linode to check the recovery console or maybe just reboot the machine, but happened to glance at the recent activity graph. Here’s the last 30 days network averages for this server – with the first big spike corresponding to the morning after I published the post. I’m not an expert, but I’m hardly a server-side dummy either. My $20/month VPS should have been able to handle this amount of traffic. Why didn’t it? Because after running WordPress since 2006, I switched to Ghost earlier this year. Not because I was dissatisfied with WP, but I just wanted to learn more about Node and I thought it would be a good way to get my feet wet. Well, that experiment ended literally last Sunday night when I decided to switch back to WordPress…and forgot to turn on caching. 🤦‍♀️ While I did go through the trouble to make sure all of my static assets were coming from a CDN…I. Forgot. To. Turn. On. WordPress. Caching. Luckily, my domain was already using Cloudflare for DNS. So I immediately logged in and turned on their caching layer (it had been off while I was testing my WordPress migration earlier that week), and within ten minutes my server naturally began responding again. Thanks, Cloudflare! And what does Cloudflare have to say about my traffic? Cloudflare Traffic Graph 513,286 requests. And keep in mind, because all of my static assets are served from another CDN (Hi, Bunny 👋), the only request being served through Cloudflare was the HTML for my post. And just for the sake of completeness, here’s what BunnyCDN‘s graphs look like for that time period… BunnyCDN Traffic Graphs So, those are the overall technical stats for the week. Before I get to the really interesting surprise at the end, let’s take a look at some more unique / esoteric observations I noticed. First, beyond the obvious conversations around Catalina that my tweet and post brought up, what was the topic that people emailed, @’d, and DM’d me about the most? If you look closely at the Catalina screenshot I posted, you’ll see my desktop’s wallpaper is a hi-res satellite image of North America. Across all the above communication channels, I had nearly fifty different people ask me where it came from and if I could send it to them. I was sorry to reply that I don’t have access to the image. Because it’s not a picture (well, yes, it is technically). It’s an app called Downlink made by the fantastic Anthony Colangelo. It sets your Mac wallpaper to a live satellite image of the continent of your choice every twenty minutes. And it’s free! Go get it! Because I’m not normally popular, I’ve mostly forgotten that WordPress spam is a thing. But holeee shit. I don’t have comments enabled on my blog because I would rather folks respond by writing on their own blogs and linking to mine. You know, like how the web used to work before Facebook and Twitter destroyed all the good in the world. But as my post climbed higher and higher on Hacker News, I started getting emails from my blog’s WordPress install telling me about new pingbacks. A small percentage were legit posts on other websites discussing what I wrote, but for the most part – nearly 200! – were spam blogs that just regurgitated what another credible website wrote or even each other. And they were all shitty, awful, wordpress.com subdomains like the following. (I’m posting an image of their URLs because I refuse to send them any google juice.) WordPress Pingback Spam I know spammers are relentless and will ultimately ruin everything, and I certainly don’t envy Facebook’s and Twitter’s bot problem, but is WordPress.com (with the help of Akismet) not capable of shutting this stuff down? I don’t know. It’s not my area of expertise. Moving on. With so many people visiting my personal website, did any of them explore further and reach my company website? Did it affect my app sales at all? Yes! Here are visits to my company website over the past 30 days: clickontyler.com visits graph And, more excitingly, here are my app sales for that period: App sales graph You can see the spike in sales and then how quickly they fall off again back towards normal. That bump certainly won’t make me rich, but it was a nice surprise. Given that this post – and most of my blog and company website – are about the Apple ecosystem, what did the device breakdown by brand look like? Device breakdown by brand Apple devices (both macOS and iOS) were 66.7%. Google (specifically Pixels, not Android as a whole) was 5.4%. The unknown segment was 12.2% 43% were from the US. San Francisco being the most popular city – not surprisingly. Ok, I’m almost ready for the real fun statistic. But one more thing before I get there. What channel sent the most traffic my way? Top referring channels Hacker News sent by far the most traffic – nearly 3:1 above Twitter in second place. But of those acquisition channels, what source provided the most engaged visitors? This is the question I’m most interested in and what I’ve been teasing throughout this entire summary post. What I want to know is average time spent by referring website. Some of these results make sense. And others I found incredibly surprising. I’ve segmented these time spent numbers to only apply to visitors of my popular blog post – those who entered my site via the short, earlier in the week one that basically just reposted my viral tweet aren’t included. instapaper.com – 7m 12s People who took the time to bookmark my post for reading later, must have really intended to read it. clickontyler.com – 5m 36s This isn’t that surprising given that these are visitors coming from my company’s website and likely already have some passing interest in my content. (And I did make sure to filter out visits from my own IP address.) mjtsai.com 4m 52s For those of you not familiar. Michael Tsai has run one of the definitive Apple developer link blogs for years. Not only does he round-up the best sources of information, he arranges them in a narrative structure that reads like a news article with his own commentary interspersed. relay.fm 4m 49s The kind folks over at the Connected podcast listed me in their show notes. I’m kinda scared to hear what their commentary was. newsblur.com 3m 13s My preferred RSS web app. theregister.co.uk 2m 26s Their reporter emailed me for a quote. I declined. So they published my email declining their request for a quote. I’m super media savvy. pinboard.in 1m 57s My favorite bookmarking service. And now I have to skip through a couple pages in my stats app to get to the good stuff. Twitter and Hacker News. Twitter, by nature, is probably going to rank low on time spent. And it did. 46s. Hacker News? There were two stories on HN last week linking to my website. The first amassed 315 comments. The second has 536 currently. And HN is generally known for having a good, mostly thoughtful comments section. So, 851 comments – many of them full paragraphs or longer – about a 2,500 word blog post that should probably take an average of ten minutes to read. Forty. Two. Seconds. Post navigation Source: Aftermath – tyler.io

    Read at 09:41 pm, Oct 21st

  • Broken – tyler.io

    This isn’t the blog post I intended to write. In fact, last night I drafted up one about my problems sending background push notifications with Amazon SNS (coming soon!). And after the ridiculously over-the-top shit-storm that blew up over my dumb tweet earlier this week, the last thing I wanted to do was step back in that arena. But this needs to be said. But, first… I’m an Apple developer. It’s the specific nerd sub-culture that I identify with the strongest. I’ve been writing and selling my own software for macOS since 2003 – back when it was still Mac OS X – back when apps were called software. And I had apps in the iOS and Mac App Stores on day one of their respective openings. I’m not rich from it, and I don’t claim to even be that successful. But for a few wonderful years my own apps were my full time income. I’m not part of the old guard of Mac developers, but I’ve been around the block and doing this for over a decade and a half. I hope I’ve earned the right to spout off my stupid opinions on the internet occasionally. And as an Apple software developer, I live through the Summer beta periods. On my secondary machine. And, in recent years, within virtual machines that allow me to do more intricate testing. I’ve seen easy-going mostly spit and polish releases as well more substantial user-facing and under-the-hood ones. But Catalina has been different in two particularly gruesome ways that get even worse when combined. The first, is purely from a stability and functional standpoint. The early betas of Catalina were really, really broken. But that’s OK! That’s what betas are for. And while I can only speak for myself, I think most developers are more than happy to offer input to Apple and report bugs. So I’m totally fine using a wonky OS for a few months on a spare machine while I test my own software in addition to Apple’s. But here’s the bad part. Apple is becoming (already is?) a services company. And, let’s face it. Apple has never been good at anything involving the internet. I feel like they could have all the money and engineers in the world (which they basically already do) and still never completely get their services right because it’s just not in their DNA. Applications are. Hardware is. But put a network layer in there and they crap themselves. (Ok, not in every case. I’m obviously exaggerating to make a point. But the overall track record is iffy at best.) And so when they decide to overhaul how CloudKit and iCloud Drive work and then merge those changes into an already buggier-than-usual beta OS, disaster can ensue. Because now those bugs – file corruptions, missing data, broken APIs and fundamental things that simply stop syncing – can spill over and infect your other Macs running a stable OS. It’s my own fault for not knowing any better and signing into my Catalina machine with my personal Apple ID, but I needed to do some iCloud development this Summer and using my own ID just made things simpler. But after I ended up with (not joking) two-hundred duplicated ~/Documents directories – each with a random assortment of duplicated files of different revisions – I swore off dealing with Catalina and iCloud for the rest of the Summer. I put all of those new features on hold and planned to pick them back up after the GM when everything stabilized. I signed out of iCloud on every Catalina machine and VM and assumed Apple would get their problems sorted by Fall. And I wasn’t alone in that assessment and strategy. Just google around for developer blog posts and tweets from the beta 3-ish time period. And that’s the puzzling and quite scary thing about all of this and the ultimate point I want to make. We (developers) were making it loud and clear that this stuff was very, very broken. And, somehow, someone at Apple made the call that it was OK to release a Public beta onto the world. A buggy, broken OS is one thing. Users installing it should know to beware. But a buggy, broken OS that also puts their data in jeopardy both on that machine and all their others linked by an Apple ID is unconscionable. And still the betas marched on. And eventually it seemed like Apple realized what they were up against and threw in the towel and reverted the OS-level iCloud changes – much like discoveryd a number of years ago. Again, I just read and heard about all of this. I was completely off iCloud on Catalina at this point and assumed the massive rollback would fix things. So when Apple officially released Catalina to the public this week without so much as a press release or heads-up to developers (yes, there had been a GM build, but still, would an email to developers ahead of time have been so difficult?), I was ready to upgrade and go all-in. Perhaps I was being naive, but I truly care about the experience my software customers have. And that means I have to live with the same system they do – even if that means dealing with OS bugs that just couldn’t be fixed in time for the .0 release. But, damn. I’ll go through some of the highlights (lowlights?) I’ve run into below, but I guess this is my thesis: The final (well, first) Catalina release along with the outright awful public beta makes me think one thing. And that is Apple’s insistence on their annual, big-splash release cycle is fundamentally breaking engineering. I know I’m not privy to their internal decision making and that software features that depend on hardware releases and vice-versa are planned and timed years (if not half-decades) in advance, but I can think of no other explanation than that Marketing alone is purely in charge of when things ship. Why else would stuff so completely broken and lacking the attention to detail that Apple is known for and (ahem) markets themselves on have shipped if not than to meet an arbitrary deadline? Apple has so many balls in the air – and this metaphor doesn’t really make any sense now that I’m typing it – but they’re all interconnected now that Apple is a services company. And as a services company they must find a way to ship features, fixes, and updates outside of the run-up to the holiday season. They need to be more (and, oh god, this word makes me want to vomit) agile. An Annotated Summary of the Catalina Crap I’ve Noticed So Far Allow or Deny Let’s start with my now infamous tweet from the other day. (I’m an influencer!) This screenshot has absolutely been manipulated to make a point, but everything in it is real. It’s all of the security warnings and permission dialogs that I ran into (and screenshotted and arranged for maximum effect) during my iMac’s first startup after installing Catalina as well as about ten minutes of poking around and launching a few apps. Hoo, boy. The point I was hoping (but probably failed) to make, is that there are many thousands of way smarter people inside Apple than me, and a frightening, pop-up frenzy that will absolutely condition non-technical users to blindly click “Allow” is the best solution they could arrive at or ship in time? Maybe they did countless user studies and determined this really is the safest and best approach. But I doubt it. I think it was a combination of poor management, hard deadlines, and probably a cavalcade of upper management and C-level executives who only use iOS as their daily driver and simply lack the imagination, experience, and technical vision to realize a modal pop-up flow that (kind of) works on a touch device does not scale to an overlapping, multiple-window, keyboard and cursor driven interface, i.e., the desktop computer. “Security” Let me go ahead and silence the Hacker News crowd and openly admit that, yes, I’m a geek, a developer, a technical person, and most definitely not a normal user. That said, there needs to be an I’m-Not-A-Dummy switch in System Preferences because all my shit’s broken and I can find zero guidance from Apple on how to fix it. I have a number of background jobs and processes on my iMac, which I basically treat as an always-on, home server. Some are run via cron, others by launchd. Some are run under my user account, others as root. A few examples: I have an AppleScript that runs every ten minutes and downloads photos from a server and imports them into Photos.app. After upgrading to Catalina, it failed every time. I stopped cron so I could debug and run it manually. The first time I execute it, Terminal.app asks for permission to access my ~/Photos directory. OK. Then it prompts to allow Terminal.app to control Photos.app. OK. And, finally, and I’m not sure why given I already granted permission for the ~/Photos directory, it asks for permission to control Finder. iTerm would like to control Finder sigh With all those permissions granted, I add a few log statements and turn cron back on. The job runs. And fails. Again. Because even though I granted permission moments ago, now that it’s being run in a slightly different way, Catalina decides to lock it down again. How is this decided by macOS and how do I fix it? My googling has failed me so far. Next. Because I’m an idiot with reasons, I have a python daemon that launches as root via launchd and remains running in the background. It is now silently failing because it isn’t allowed to access an external USB drive. Oh, and while debugging the AppleScript example from a paragraph above, every time I saved my cron changes in vim, the system would throw up a dialog asking for my permission to allow Terminal to modify, you know, my own personal crontab that I explicitly invoked an editing session of. (Although I’m pretty sure this was also a thing in Mojave. But the point still stands.) I guess Apple is trying to protect less-technical customers who might inadvertently install a malicious recurring background process as root? Or accidentally read a file from an external volume while running a shell script that this non-technical user opened up a Terminal, edited, made executable, and invoked themselves? I suppose there’s an attack vector there. iCloud Password Shenanigans After upgrading to Catalina, like basically every other recent macOS release, I found myself logged out of iCloud. Facebook and Gmail have never once in my life expired my session on purpose. But since my iCloud data is stored on an encrypted, non-removable hard drive, protected by a T2 chip and biometric security, I can see why it’s best if Apple logs me out every time I install a software update. OK. Fine. I’ll log back in, but of course iCloud rejects my password in System Preferences so many times that they eventually lock me out and force me through a forgot password flow just so I can change my password back to what it always was. That done and finally logged back in, all of my iOS devices start beeping and I find this: My Apple ID is being used on a new device apparently. To be expected given that I just logged in on a new(?) device. But, why is my iMac’s hostname now duplicated “(2)”? Reasons, I’m sure. Local Account Password Shenanigans After the Catalina upgrade and spending some time getting my apps, settings, etc. kind of back to normal, my wife tried to login to her account on that iMac. Everything went fine, and we got some of her software updated, too. But then the Mac went to sleep. Her local Mac account password is a simple, all-lowercase English word without spaces or numbers or special characters. macOS wouldn’t accept it when she tried to login again. It wouldn’t accept it when I typed it in. Nor when I decided the keyboard must have somehow malfunctioned during the last half hour and I thought I was extremely clever by trying to login as her via a remote screen sharing session and it failed as well. That was about 36 hours ago and the problem persists through multiple restarts. I don’t have it in me right now to try and debug this. But I’m not worried. All of her data is backed up. I’m ready to blow away her account and create a new one. But,…Apple? Photos.app It took around eight hours for Photos.app to upgrade my 200GB iCloud Photos library the first time I opened it on Catalina. Since then, across multiple reboots, it simply refuses to update with new photos added to iCloud from other devices. Or upload new photos to iCloud that I imported directly on that machine. It just says “Updating…”, forever. More Little Things I found earlier today that I couldn’t restart my Mac because an application was still running. The only thing open (but idle) was Xcode. I did a quick ⌥⌘⎋ and discovered this: System Preferences is not responding I had no idea System Preferences was even running. It wasn’t visible in the Dock? As I mentioned earlier, I had to sign in to iCloud again (a few times) after upgrading. A day later, this popped up while I was using TextMate: Can't connect to FaceTime 🤷‍♂️ After upgrading to Catalina, macOS made me reauthorize every app that wanted to send me notifications. Ironically, the following alert appears every time I reboot despite always dismissing it using the most definitive option Apple provides and never giving whatever-process-is-showing-it permission to notify me of anything in the first place: Welcome to macOS Catalina. You're in for a treat! Anyway… I love the Mac and everything its software and hardware stand for. The iMac Pro and new Mac mini are phenomenal. The revamped Mac Pro (six years? really?) is a damn beast. And, honestly, I don’t even mind USB-C. But the keyboards, the literally hundreds if not thousands of predatory scams on the Mac App Store, whatever the fuck is going on with Messages.app on macOS, iCloud Drive, the boneheaded, arrogant, literally-put-on-the-consumer-facing-marketing-website claim that iPad-to-Mac with Catalyst was merely a checkbox, all the dumb, stupid little bugs I mentioned above, and the truckload of other paper-cuts I’m sure to run into once I’m on Catalina for more than 48 hours… My god. It is absolutely clear that the Mac is far outside of what the upper-ranks of Apple is focusing on. I’m not trying to throw Engineering under the bus. I’m friends with many wonderful, talented, hard-working, and caring Apple developers who want the Mac to fucking thrive. What I am doing is explicitly shitting on management and blaming the executive team for allowing all of the above to ship. Post navigation Source: Broken – tyler.io

    Read at 09:36 pm, Oct 21st

  • macOS 10.15 Vista – tyler.io

    I completely realize and wholeheartedly own-up to the fact that I’m a geek and a Mac power user above and beyond what normal muggles will ever experience, nonetheless, this is the first-run experience I was greeted to this afternoon after upgrading to Catalina. Another update… It’s now 48 hours later. Ignore my original update below. Go read this instead. Update four hours later… Well, that escalated quickly. I’ve been on Twitter for twelve years, and OF COURSE my two biggest tweets would be me making a dumb joke and dunking on Apple. Anyway, the screenshot above deserves a full explanation. First of all, it’s about 95% accurate. I took it after upgrading an existing Mojave system to Catalina this afternoon. Once the installer finished and I worked my way through the usual post-installation prompts/windows/whatever, I left and took my son to go get a flu shot. (Glamorous life of a father and all that.) When I came back about forty minutes later, that’s basically how the screen looked. I thought it was mildly funny and began arranging all of the permission dialogs so they didn’t overlap. And that’s when all the “XXXX would like to show notifications” prompts appeared. So I took another screenshot. Soon after that, I realized that – like with nearly every macOS update – I had been logged out of iCloud, which meant time for a screenshot yet again. I only spent about ten minutes on that system today. But it was enough time to capture all of these papercuts and combine them into one truly-awful über screenshot. I want to make clear that I’m not blaming the talented Apple engineers who obviously worked their butts off on Catalina just like they do every release. My side-eye is squarely directed at the managers and Marketers who push for such an insane release cycle. And also at the executives who – shiny new Mac Pro and XDR display be damned – obviously don’t see the Mac as a priority any longer. From laptop keyboards to the Mac App Store to the insane new focus on Services, it’s clear the higher-ups at our favorite fruit company just don’t give a shit any longer. And I’m not angry. I’m just sad. Post navigation Source: macOS 10.15 Vista – tyler.io

    Read at 08:09 pm, Oct 21st

  • Malloc Can Double Multi-threaded Ruby Program Memory Usage

    Summary: Memory fragmentation is difficult to measure and diagnose, but it can also sometimes be very easy to fix. Let's look at one source of memory fragmentation in multi-threaded CRuby programs: malloc's per-thread memory arenas. (3343 words/20 minutes) Sometimes, it really is that simple.

    Read at 01:32 pm, Oct 21st

  • Trump Publicly Urges China to Investigate the Bidens

    President Trump made a similar, but private, request of the president of Ukraine, an episode that has prompted an impeachment inquiry.

    Read at 01:24 pm, Oct 21st

Day of Oct 20th, 2019

  • WordPress.org Bumps PHP Maximum for Plugin Directory to Version 7.2

    The WordPress.org SVN system received a version bump to 7.2 on October 3. This change means that plugin authors can now use newer PHP syntax in plugins they submit to the official plugin directory. In the future, the version maximum will match what’s running on WordPress.org.

    Read at 11:33 pm, Oct 20th

  • https://levelup.gitconnected.com/build-a-pwa-using-only-vanilla-javascript-bdf1eee6f37a

    Read at 05:33 pm, Oct 20th

  • Matt Mullenweg and David Heinemeier Hansson Discuss WordPress Market Share, Monopolies, and Power in Open Source Communities

    In what began as a heated conversation on Twitter, Automattic CEO Matt Mullenweg and Ruby on Rails creator and Basecamp co-founder David Heinemeier Hansson took to the airwaves to debate opposing viewpoints on market share, monopolies, and power in open source communities.

    Read at 05:28 pm, Oct 20th

  • Trump’s Impeachment Saga Stems From a Political Hit Job Gone Bad

    Photographer: Chris Kleponis/Bloomberg The irony of President Trump’s sudden impeachment peril is that it’s the unintended result of an effort to help him: a political hit job aimed at a likely opponent (Joe Biden) and funded by a major right-wing donor (Rebekah Mercer) that Trump and his lawyer

    Read at 05:21 pm, Oct 20th

  • Giuliani consulted on Ukraine with imprisoned Paul Manafort via a lawyer

    In his quest to rewrite the history of the 2016 election, President Trump’s personal attorney has turned to an unusual source of information: Trump’s imprisoned former campaign chairman Paul Manafort. Rudolph W.

    Read at 05:15 pm, Oct 20th

  • Trump involved Pence in efforts to pressure Ukraine’s leader, though officials say vice president was unaware of allegations in whistleblower complaint

    President Trump repeatedly involved Vice President Pence in efforts to exert pressure on the leader of Ukraine at a time when the president was using other channels to solicit information that he hoped would be damaging to a Democratic rival, current and former U.S. officials said.

    Read at 05:08 pm, Oct 20th

  • Battle lines drawn after Clinton and Gabbard exchange insults

    There are fresh battles lines in the 2020 presidential campaign reflecting an unpredictable rivalry between two Democratic politicians — one who isn’t even running this cycle and another who is polling at barely one percent.

    Read at 02:21 pm, Oct 20th

  • Let's talk about what we'd be talking about if Julian Castro was white

    Though he did not have what many would categorize as a “breakout moment” during the most recent Democratic presidential primary debate, Julián Castro did offer another reminder of why his candidacy has been essential to the primary itself — even if most observers have failed to recognize it.

    Read at 02:19 pm, Oct 20th

  • Hillary Clinton suggests Putin has kompromat on Trump, Russia will back Tulsi Gabbard third-party bid

    At Tuesday night’s Democratic presidential debate, Rep. Tulsi Gabbard (D-Hawaii) hit back at critics who charged she’s too close to Russia. “This morning, a CNN commentator said on national television that I’m an asset of Russia,” she said. “Completely despicable.”

    Read at 04:33 am, Oct 20th

  • Brexit vote set to be delayed in extraordinary move by MPs to prevent risk of no-deal

    Saturday’s vote to decide Brexit is set to be delayed, after an extraordinary procedural move by MPs who fear the UK could still crash out of the EU without a deal.

    Read at 04:29 am, Oct 20th

  • NoRedInk – Building a Live-Validated Signup Form in Elm

    This has been updated for Elm 0.17. In a previous post, I touched on how we gently introduced Elm to our production stack at NoRedInk. But we didn’t stop there! Since that first course of Elm tasted so good, we went back for seconds, then thirds, and now we use Elm with The Elm Architecture for as much of our front-end as possible. We spend about 95% of our front-end programming time writing Elm, and React has become strictly legacy code. Where many languages built for the browser address the question “How can we improve JS?” Elm asks a bigger one: “how can we have the best experience making user interfaces?” This back-to-basics focus can make parts of Elm unfamiliar at first, but it enables things like a package system where semantic versioning is automatically enforced for every package and large, complex applications whose production Elm code has yet to throw a runtime exception. Let’s explore these benefits by building a live-validated signup form in Elm! Rendering a View Before we get to some code, you’ll probably want an editor plugin to make your code look nice. It’s optional, but I also highly recommend also installing elm-format by Aaron VonderHaar, which you can use to instantly format your source files every time you save them. It saves a lot of time fiddling with code formatting! If you already have npm, you can get the Elm compiler by running npm install -g elm (or alternatively you can just download the Elm installer). Afterwards you should be able to run this command and see the following output: $ elm-make --version elm-make 0.17 (Elm Platform 0.17.0) …followed by some help text. Make sure the Elm Platform version is 0.17.0! Let’s start by rendering the form. Where JavaScript has Handlebars, CoffeeScript has Jade, and React has JSX, Elm also has its own declarative system for rendering elements on a page. Here’s an example: view model =    form [ id "signup-form" ] [        h1 [] [ text "Sensational Signup Form" ],        label [ for "username-field" ] [ text "username: " ],        input [ id "username-field", type' "text", value model.username ] [],        label [ for "password"] [text "password: " ],        input [ id "password-field", type' "password", value model.password ] [],        div [ class "signup-button" ] [ text "Sign Up!" ]    ] Like any templating system, we can freely substitute in values from the model. However, unlike Handlebars, Jade, and JSX, this is not a special templating library with its own special syntax - it’s just vanilla Elm code! (You can check out the Elm Syntax Reference for more.) The above code simply defines a function called view with one argument (model). Much like Ruby and CoffeeScript, everything is an expression in Elm, and the last expression in an Elm function is always used as its return value. In other words, the above code would look essentially like this in JavaScript: function view(model) {    return form([ id("signup-form") ], [        h1([], [ text("Sensational Signup Form") ]),        label([ for("username-field") ], [ text("username: ") ]),        input([ id("username-field", type_("text"), value(model.username) ], []),        label([ for("password") ], [ text("password: ") ]),        input([ id("password-field", type_("password"), value(model.password) ], []),        div([ class("signup-button") ], [ text("Sign Up!") ])    ]); } Notice that in Elm you call functions with spaces rather than parentheses, and omit the commas that you’d put between arguments in JS. So in JavaScript you’d write: Math.pow(3, 7) …whereas in Elm you’d write: Math.pow 3 7 When using parentheses to disambiguate, you wrap the entire expression like so: Math.pow (Math.pow 3 7) 4 On a stylistic note, the Elm Style Guide recommends putting commas at the beginning of the line: view model = form [ id "signup-form" ] [ h1 [] [ text "Sensational Signup Form" ] , label [ for "username-field" ] [ text "username: " ] , input [ id "username-field", type' "text", value model.username ] [] , label [ for "password" ] [ text "password: " ] , input [ id "password-field", type' "password", value model.password ] [] , div [ class "signup-button" ] [ text "Sign Up!" ] ] When I first started using Elm I thought this looked weird, and kept using the trailing-commas style I was accustomed to. Eventually I noticed myself spending time debugging errors that turned out to have been caused because I’d forgotten a trailing comma, and realized that with leading commas it’s blindingly obvious when you forget one. I gave this style a try, got used to it, and now no longer spend time debugging those errors. It’s definitely been worth the adjustment. Give it a shot! Since Elm compiles to a JavaScript file, the next step is to use a bit of static HTML to kick things off. We’ll be using a file that includes a basic HTML skeleton, some CSS styling, a tag to import our compiled .js file (the Elm standard library is about the size of jQuery), and one line of JavaScript that kicks off our Elm app: Elm.SignupForm.embed(document.getElementById("elm-rendering-area")); We’re choosing to have the Elm app render to a particular div, but we could also have it run without rendering—for example, to add a touch of Elm to an existing JavaScript code base. Finally we need to surround our view function with some essentials that every Elm program needs: naming our module, adding relevant imports, and declaring our main function. Here’s the resulting SignupForm.elm file, with explanatory comments: SignupForm.elm module SignupForm exposing (..) -- This is where our Elm logic lives.`module SignupForm` declares that this is -- the SignupForm module, which is how other modules will reference this one -- if they want to import it and reuse its code. import Html.App -- Elm’s "import" keyword works similarly to "require" in node.js. import Html exposing (..) -- The “exposing (..)” option says that we want to bring the Html module’s contents -- into this file’s current namespace, so that instead of writing out -- Html.form and Html.label we can use "form" and "label" without the "Html." import Html.Events exposing (..) -- This works the same way; we also want to import the entire -- Html.Events module into the current namespace. import Html.Attributes exposing (id, type', for, value, class) -- With this import we are only bringing a few specific functions into our -- namespace, specifically "id", "type'", "for", "value", and "class". view model = form [ id "signup-form" ] [ h1 [] [ text "Sensational Signup Form" ] , label [ for "username-field" ] [ text "username: " ] , input [ id "username-field", type' "text", value model.username ] [] , div [ class "validation-error" ] [ text model.errors.username ] , label [ for "password" ] [ text "password: " ] , input [ id "password-field", type' "password", value model.password ] [] , div [ class "validation-error" ] [ text model.errors.password ] , div [ class "signup-button" ] [ text "Sign Up!" ] ] -- Take a look at this starting model we’re passing to our view function. -- Note that in Elm syntax, we use = to separate fields from values -- instead of : like JavaScript uses for its object literals. getErrors model = { username = if model.username == "" then "Please enter a username!" else "" , password = if model.password == "" then "Please enter a password!" else "" } initialErrors = { username = "", password = "" } main = Html.App.program { init = ( initialModel, Cmd.none ) , view = view , update = update , subscriptions = _ -> Sub.none } The starting model we’re passing to the view is: { username = "", password = "" } This is a record. Records in Elm work pretty much like Objects in JavaScript, except that they’re immutable and don’t have prototypes. Let’s build this, shall we? $ elm-make SignupForm.elm Some new packages are needed. Here is the upgrade plan. Install: elm-lang/core 4.0.1 Do you approve of this plan? (y/n) Ah! Before we can build it, first we need to install dependencies. elm-lang/core is a basic dependency needed by every Elm program, so we should answer y to install it. Now we should see this: Downloading elm-lang/core Packages configured successfully! I cannot find module 'Html.App'. Module 'SignupForm' is trying to import it. Potential problems could be: * Misspelled the module name * Need to add a source directory or new dependency to elm-package.json Ah, right. Because we’re using features from the html package in addition to core, we’ll need to install that explicitly: $ elm-package install elm-lang/html To install evancz/elm-html I would like to add the following dependency to elm-package.json:    "elm-lang/html": "1.0.0 The package installer wants to create an elm-package.json file for you. How nice of it! Enter y to let it do this. Next it will tell you it needs a few dependencies in order to install elm-html, followed by: Do you approve of this plan? (y/n) Again, enter y to install the virtual-dom dependency as well as html. Once it’s done, we have our dependencies installed. We’re ready to build! Run this to build your Elm code: $ elm-make SignupForm.elm After this completes, you should see: Successfully generated index.html. Sure enough, the current folder will now have an index.html file which contains your Elm code compiled to inline JavaScript. By default, elm-make generates this complete .html file so that you can get something up and running with minimal fuss, but we’ll compile it to a separate .js file in a moment. You should also now have an elm-stuff/ folder, which contains cached build data and your downloaded packages - similar to the node_modules/ folder for your npm packages. If you’re using version control and like to put node_modules in your ignore list, you’ll want elm-stuff/ in your ignore list as well. If you open up index.html in a browser, you’ll see an unstyled, fairly disheveled-looking signup form. Let’s improve on that by splitting out the JavaScript so we can put it in some nicer surroundings. $ elm-make SignupForm.elm --output elm.js Success! Compiled 0 modules. Successfully generated elm.js The --output option specifies the file (either .html or .js) that will contain your compiled Elm code. See how it says Compiled 0 modules? This is because elm-make only bothers rebuilding parts of your code base that changed, and since nothing changed since your last build, it knows nothing needed to be rebuilt. All it did was output the results of its previous build into elm.js. Go ahead and download the HTML boilerplate snippet as example.html, put it in the same directory as your elm.js file, and open it. Since example.html loads a file called elm.js, you should now see a signup form like so: Congratulations! You’ve now built a simple static Elm view and rendered it in the browser. Next we’ll add some interaction. Expanding the Model Let’s add a touch of interactivity: when the user submits an empty form, display appropriate error messages. First, we’ll introduce some validation error messages to our model, and render them in the view: view model = form [ id "signup-form" ] [ h1 [] [ text "Sensational Signup Form" ] , label [ for "username-field" ] [ text "username: " ] , input [ id "username-field", type' "text", value model.username ] [] , div [ class "validation-error" ] [ text model.errors.username ] , label [ for "password" ] [ text "password: " ] , input [ id "password-field", type' "password", value model.password ] [] , div [ class "validation-error" ] [ text model.errors.password ] , div [ class "signup-button" ] [ text "Sign Up!" ] ] If we wrote this same logic in JavaScript, on page load we’d get a runtime exception: “cannot read value "username" of undefined.” Oops! We’re trying to access model.errors.username in our view function, but we forgot to alter the initial model in our main function to include an errors field. Fortunately, Elm’s compiler is on top of things. It will actually figure out that we’ve made this mistake and give us an error at build time, before this error can reach our users! $ elm-make SignupForm.elm --output elm.js ==================================== ERRORS ==================================== -- TYPE MISMATCH ------------------------------------------------ SignupForm.elm The argument to function `view` is causing a mismatch. 48| view { username = "", password = "" } ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Function `view` is expecting the argument to be: { b | ..., errors : ... } But it is: { ... } Detected errors in 1 module. Let’s take a closer look at what it’s telling us. Function `view` is expecting the argument to be: { b | ..., errors : ... } But it is: { ... } This tells us that our view function is expecting a record with a field called errors (among other fields - hence the ...), but the record we’ve provided does not have an errors field! Sure enough, we totally forgot to include that. Whoops. The compiler knows this is broken because we pass that record into our view function as the model argument, and the view function then references model.errors.username and model.errors.password - but given the code we’ve written, there’s no possible way model.errors could be defined at that point. This is a classic example of the Elm compiler saving us from a runtime exception. It’s really, really good at this - so good that our entire Elm codebase (around 30,000 lines of Elm code) still hasn’t thrown a single runtime exception in production! Now that we know about the problem, we can fix it by adding an errors field like so: initialErrors = { username = "bad username", password = "bad password" } main = view { username = "", password = "", errors = initialErrors } Let’s rebuild with elm-make SignupForm.elm --output elm.js (which should succeed now) and open the browser. We should see this: Incidentally, in a larger project we’d probably use a build tool plugin like elm-webpack-loader or gulp-elm or grunt-elm rather than directly running elm-make every time, but for this example we’ll keep things simple. Before we move on, let’s make initialErrors start out empty, as the user should not see “bad username” before having had a chance to enter one: initialErrors = { username = "", password = "" } Next we’ll update the model when the user interacts. Adding Validation Logic Now that we’re rendering errors from the model, the next step is to write a validation function that populates those errors. We’ll add it right before our main declaration: getErrors model = { username = if model.username == "" then "Please enter a username!" else "" , password = if model.password == "" then "Please enter a password!" else "" } For comparison, here’s how this would look in (similarly formatted) JavaScript: function getErrors(model) {    return {        username:            (model.username === ""                ? "Please enter a username!"                : ""),        password:            (model.password === ""                ? "Please enter a password!"                : "")    }; } To recap: we have a starting model, a validation function that can translate that model into error strings, and a view that can report those errors to the user. The next step is to wire it all together. When the user clicks Sign Up, we should use all the pieces we’ve built to display any appropriate errors. How do we do that? With a call to Html.App.program! Let’s replace our main implementation with the following: main = Html.App.program { init = ( initialModel, Cmd.none ) , view = view , update = update , subscriptions = _ -> Sub.none } Html.App.program gives us an interactive application that follows The Elm Architecture. It needs four ingredients: init, view, update, and subscriptions. We already have a view and we won’t be using subscriptions today, so the next step is to specify update and init. Let’s add update below our getErrors function: update msg model = if msg.msgType == "VALIDATE" then ( { model | errors = getErrors model }, Cmd.none ) else ( model, Cmd.none ) This update function’s job is to take a message (a value that describes what we want done) and a model, and return two things: A new model, with any relevant updates applied A description of any commands we want run - such as firing AJAX requests or kicking off animations. We’ll dive into using commands later, but for now we’ll stick to no commands: Cmd.none Have a look at what the else branch returns: ( model, Cmd.none ) Whenever you see a comma-separated list of elements inside parentheses like this, what you’re seeing is a tuple. A tuple is like a record where you don’t bother naming the fields; ( model, Cmd.none ) is essentially a variation on { model = model, command = Cmd.none } where fields are accessed by position rather than by name. Here the else branch returns a tuple containing the original model unchanged, as well as Cmd.none - indicating we do not want any effects like AJAX requests fired off. Compare that to the if branch: if msg.msgType == "VALIDATE" then ( { model | errors = getErrors model }, Cmd.none ) This says that if our msg record has a msgType field equal to "VALIDATE" then we should return an altered model and (once again) Cmd.none. The first element in this tuple is an example of a record update: { model | errors = getErrors model } You can read this as “Return a version of model where the errors field has been replaced by the result of a call to getErrors model.” (Remember that records are immutable, so an “update” doesn’t actually change them; rather, an “update” refers to returning a separate record which has had the update applied.) Since our view is already displaying the result of model.errors, and getErrors returns an appropriate list of errors, all that’s needed to perform the validation is to pass this update function a message record like { msgType = "VALIDATE", payload = "" } and we’ll end up displaying the new errors! Now we’re ready to connect what we’ve done to a click handler to our submit button. We’ll change it from this: , div [ class "signup-button" ] [ text "Sign Up!" ] …to this: , div [ class "signup-button", onClick { msgType = "VALIDATE", payload = "" } ] [ text "Sign Up!" ] onClick is a function which takes a message—in this case the plain old record { msgType = "VALIDATE", payload = "" }. When the user clicks this button, the record { msgType = "VALIDATE", payload = "" } will be sent to update as the message describing what we want done. update will also recieve the current model, thus connecting view and update. Finally, you may have noticed that init is currently defined in terms of initialModel, which we haven’t specified yet. initialModel will represent the model we want to have when the application starts up; we can define it right above main like so: initialModel = { username = "", password = "", errors = initialErrors } Let’s build and try to use what we’ve put together so far. Alas and alack! As soon as we submit the form, it clears out everything we’ve typed and presents us with validation errors. What gives? This is because when we submit the form, it re-renders everything based on the values in the current model…and we never actually updated those! No matter how much you type into either the username field or the password field, those values will not make it into the model of their own free will. We can fix that using the same technique as before. First we’ll add to our update function: update msg model =    if msg.msgType == "VALIDATE" then        ( { model | errors = getErrors model }, Cmd.none )    else if msg.msgType == "SET_USERNAME" then        ( { model | username = msg.payload }, Cmd.none )    else if msg.msgType == "SET_PASSWORD" then        ( { model | password = msg.payload }, Cmd.none )    else        ( model, Cmd.none ) Now if this function receives something like { msgType = "SET_USERNAME", payload = "rtfeldman" }, it will update the model’s username field accordingly. Let’s create an onInput handler (since input events fire not only on keyup, but also whenever a text input otherwise changes - e.g. when you right-click and select Cut), which will dispatch one of these new messages: , input [ id "username-field" , type' "text" , value model.username , onInput (str -> { msgType = "SET_USERNAME", payload = str }) ] [] This introduces some new syntax: (str -> { msgType = …). This is how you write an anonymous function in Elm. The JavaScript equivalent of this would be: onInput(function(str) { return { msgType: "SET_USERNAME", payload: str }; }) Now let’s give password the same treatment: view actionDispatcher model = form [ id "signup-form" ] [ h1 [] [ text "Sensational Signup Form" ] , label [ for "username-field" ] [ text "username: " ] , input [ id "password-field" , type' "password" , value model.password , onInput (str -> { msgType = "SET_PASSWORD", payload = str }) ] [] , div [ class "validation-error" ] [ text model.errors.username ] , label [ for "password" ] [ text "password: " ] , input [ id "password-field" , type' "password" , value model.password , onInput (str -> { msgType = "SET_PASSWORD", payload = str }) ] [] , div [ class "validation-error" ] [ text model.errors.password ] , div [ class "signup-button", onClick actionDispatcher { msgType = "VALIDATE", payload = "" } ] [ text "Sign Up!" ] ] Now whenever the user types in the username field, a message is created (with its msgType field being "SET_USERNAME" and its payload field being the text the user entered. Remember that messages are just data; they don’t intrinsically do anything, but rather describe a particular model change so that other logic can actually implement that change. Try it out! Everything should now work the way we want. One great benefit of this architecture is that there is an extremely obvious (and guaranteed!) separation between model and view. You never have model updates sneaking into your view, causing hard-to-find bugs, because views aren’t even capable of updating models. Each step of the process is a straightforward transformation from one sort of value to the next: inputs to messages to model to view. It may not be what you’re used to, but once you get in the Elm groove, debugging and refactoring get way easier. Based on my experience writing production Elm at NoRedInk, I can confirm that these things stay easier as your code base scales. If you’re curious about the details of how our experience getting started with Elm went, check out 6 Months of Elm in Production. Asking a server about username availability Finally we’re going to check whether the given username is available. Rather than setting up our own server, we’re just going to use GitHub’s API; if GitHub says that username is taken, then that’s our answer! To do this, we’ll need to use the elm-http library to communicate via AJAX to GitHub’s API. Let’s start by installing it: elm-package install evancz/elm-http --yes (The --yes flag saves us time by automatically answering yes to all the prompts.) We’ll also need to import the Http module below our other import statements: import Http Up to this point we haven’t written any code that deals with effects. All of our functions have been stateless; when they are given the same inputs, they return the same value, and have no side effects. Now we’re about to…continue that trend! Not only does Elm represent messages as data, it represents effects as data too—specifically, using values called Tasks. Tasks have some things in common with both Promises and callbacks from JavaScript. Like Promises, they can be easily chained together and have consistent ways of representing success and failure. Like callbacks, instantiating Tasks doesn’t do anything right away; you can instantiate a hundred Tasks if you like, and nothing will happen until you hand them off to something that can actually run them. Like both Promises and callbacks, Tasks can represent either synchronous or asynchronous effects. Running a task has two steps. Chain as many Tasks as we want together. Wrap the final Task chain in a Command, whose job is to execute the chain. This is broken down into two steps because some operations in Elm are chainable, while others are not. The chainable ones have a Task-based API, whereas the non-chainable ones expose Commands only. (You’ll encounter these Commands when doing JavaScript interop. As soon as a Command finishes, Elm will run update passing the Command’s result (translated into a message), and from there we can proceed using the same update techniques we’ve used up to this point. Let’s update our "VALIDATE" message to fire off a request to GitHub’s API. In normal Elm code we wouldn’t be this verbose about it, but for learning purposes we’ll break everything down into small pieces. if msg.msgType == "VALIDATE" then    let        url =            "https://api.github.com/users/" ++ model.username        failureToMsg err =            { msgType = "USERNAME_AVAILABLE", payload = "" } successToMsg result =            { msgType = "USERNAME_TAKEN", payload = "" }        request =            Http.get (succeed "") url        cmd =            Task.perform failureToMsg successToMsg request    in        ( { model | errors = getErrors model }, cmd ) That’s a decent bit of new stuff, so let’s unpack it! First, the conditional is now using a let expression. These allow you to make local declarations that are scoped only to the let expression; the rest of our program can’t see things like url or request, but anything between let and the expression just after in can see them just fine. The whole let expression ultimately evaluates to the single expression after in. Since url is a plain old string, and failureToMsg and successToMsg are plain old functions, let’s move on to something we haven’t seen before: Http.get. It takes two arguments: a Decoder and a URL string. The URL is what you’d expect, and the decoder is like targetValue from our input event handler earlier: it describes a way to translate an arbitrary JavaScript value into something more useful to us. In this case, we’re not using a very interesting Decoder because any API response error means the username was not found (and thus is available), and any success means the username is taken. Since the Decoder only gets used if the API response came back green, we can use the succeed Decoder, which always decodes to whatever value you give it. (succeed "" means “don’t actually decode anything; just always produce "" no matter what.”) Finally we have the call to Task.perform, which translates Tasks into Commands. But what are those first two arguments we’re passing it? Like JavaScript Promises, Elm Tasks can either succeed or fail, and Http.get returns a Task that can most definitely fail (with a network error, 404 Not Found, and so on). However, Commands always result in a call to update, and update requires a message! As such, we need to convert both successful Task results and failed Task results into messages. Fortunately, the Task.perform function does exactly that. It accepts two arguments in addition to the task in question: A function that translates a failure result into a message (in our case, failureToMsg) A function that translates a success result into a message (in our case, successToMsg) Armed with these functions and a Task, Task.perform can give us the Command we desire. We’re all set! Now when we enter a username, if that username is available on GitHub (meaning the Http.get results in a 404 Not Found error and thus a failed Task), the result is { msgType = "USERNAME_AVAILABLE", payload = "" } being passed to our update function. If the username is taken (meaning the Http.get resulted in a successful Task), the result is that our “always results in usernameTakenTask” Decoder causes { msgType = "USERNAME_TAKEN", payload = "" } to be passed to our update function instead. Let’s incorporate these new messages into our update function: update msg model =    …    else if msg.msgType == "USERNAME_TAKEN" then        ( withUsernameTaken True model, Cmd.none )    else if msg.msgType == "USERNAME_AVAILABLE" then        ( withUsernameTaken False model, Cmd.none )    else        ( model, Cmd.none ) withUsernameTaken isTaken model =    let        currentErrors =            model.errors        newErrors =            { currentErrors | usernameTaken = isTaken }    in        { model | errors = newErrors } Before we can use all these fancy new libraries, we’ll need to import them at the top: import Http import Task exposing (Task) import Json.Decode exposing (succeed) Finally, we need to introduce the “username taken” validation error to initialErrors, getErrors, and view: initialErrors =    { username = "", password = "", usernameTaken = False } getErrors model =    …    , usernameTaken = model.errors.usernameTaken    } view actionDispatcher model =    …    , div [ class "validation-error" ] [ text (viewUsernameErrors model) ]    … viewUsernameErrors model =   if model.errors.usernameTaken then       "That username is taken!"   else       model.errors.username Here’s our final result. Now the username field is validated for availability, and everything else still works as normal. We did it! Wrapping Up We’ve just built a live-validated signup form with the following characteristics: Clear, obvious, and strict separation between model and view A compiler that told us about type errors before they could become runtime exceptions High-performance reactive DOM updates like React and Flux Live AJAX validation of whether a username is available Immutability everywhere Not a single function with a side effect To save time, we didn’t do everything completely idiomatically. The fully idiomatic approach would have represented messages using the more concise and robust union types and case expressions instead of records and if/then/else expressions, and to document various functions using type annotations. Here are some great resources for learning more about idiomatic Elm: By the way, if working on Elm code in production appeals to you…we’re hiring! Richard Feldman @rtfeldman Engineer at NoRedInk Thanks to Joel Clermont, Johnny Everson, Amjith Ramanujam, Jonatan Granqvist, Brian Hicks, Mark Volkmann, and Michael Glass for reading drafts of this. Source: NoRedInk – Building a Live-Validated Signup Form in Elm

    Read at 03:36 am, Oct 20th

  • NoRedInk – Walkthrough: Introducing Elm to a JS Web App

    4 years ago /.post-date /.post-title /.post-head When we introduced Elm to our production front-end at [NoRedInk](https://www.noredink.com/jobs), we knew what to expect: the productivity benefits of an incredibly well-built language, and the intrinsic risk of integrating a cutting-edge technology for the first time. Knowing these risks, we came up with a minimally invasive way to introduce Elm to our stack, which let us get a sense of its benefits without having to make a serious commitment. Since several people have asked me about this, I’d like to share how we did it! This walkthrough assumes only a beginner level of Elm familiarity. I won’t be spending much time on syntax (though I will link to the [syntax guide](http://elm-lang.org/docs/syntax) as a reference) or functional programming staples like [map](http://package.elm-lang.org/packages/elm-lang/core/2.1.0/List#map) and [filter](http://package.elm-lang.org/packages/elm-lang/core/2.1.0/List#filter), so I can go into more depth on introducing Elm to an existing JS codebase. Let’s dive in! ## Elm for…business logic? Elm is known for its UI-crafting capabilities (both as [an improvement over React](http://elm-lang.org/blog/blazing-fast-html) as well as for [game programming](https://github.com/BlackBrane/destroid)), but building out an entire UI in a new language was more commitment than we wanted to tackle as a starting point. We didn’t want a drink from the fire hose; we just wanted a little sip. Fortunately, it turns out Elm is also a phenomenal language for writing business logic. Besides making it easy to write clear, expressive code, Elm also makes it easy to write code that’s rock-solid reliable. How reliable? Here are some numbers: * Students answer over 2.5 million questions per day on NoRedInk. Needless to say, edge cases happen. * [3% of our code base](https://twitter.com/rtfeldman/status/628433529538412544) (so far!) is Elm code. Not a ton, but a substantial chunk. * We have yet to see our production Elm code cause a single runtime exception. This is not because we sheltered it; our production Elm code implements some of the most complicated parts of our most mission-critical features. Despite that level of exposure, every runtime browser exception we’ve ever encountered—in the history of the company—has been fixed by changing CoffeeScript or Ruby code. Never by changing Elm code. This is also not because we have some particularly unusual code base; rather, it’s because Elm’s compiler is astonishingly helpful. Let’s get a taste for that by introducing Elm to an existing JavaScript code base! **Adding a hint of Elm to a JS TodoMVC app** We’ve been using [React](http://reactjs.com/) and [Flux](https://facebook.github.io/flux/docs/overview.html) at NoRedInk since they came out in 2014. Flux stores are a convenient place to introduce Elm to your code base, because they’re all about business logic and are not concerned with UI rendering. Porting a single Flux store to Elm proved a great starting point for us; it was enough to start seeing real benefits, but not such a big commitment that we couldn’t have changed our minds and turned back if we’d wanted to. Let’s walk through doing the same thing: porting a store from the [Flux TodoMVC](https://github.com/facebook/flux/blob/master/examples/flux-todomvc/js/stores/TodoStore.js) example to Elm. First, grab Facebook’s [Flux](https://github.com/rtfeldman/flux) repo. (I’m linking to a fork of their repo, in case theirs has changed since I wrote this.) and `cd` into its directory. At this point you should be able to [follow the instructions for building the TodoMVC example](https://github.com/rtfeldman/flux/tree/35b040e213149b9577dc253d9ca1a4ba086f1159/examples/flux-todomvc#running). git clone git@github.com:rtfeldman/flux.git cd examples/flux-todomvc npm install npm start Once you have npm start running, you should be able to open the index.html file in your current directory and see a working app. If you’re not familiar with the Flux architecture (it’s conceptually similar to The Elm Architecture), now might be a good time to read about it. You don’t need a ton of experience with it to follow along, but having a general idea what’s going on should help. Now you’ll want to grab yourself a copy of Elm. Either use npm install -g elm or visit elm-lang.org/install to get an installer. When you’re finished, run elm make --help in your terminal and make sure it says Elm Platform 0.15.1 at the top! Integrating Elm at a basic level requires three things: A .elm file containing our Elm code. An elm-package.json file specifying our dependencies. A JavaScript command to invoke our Elm code. Let’s create examples/flux-todomvc/TodoStore.elm with the following code: module TodoStore where port dispatchCreate : Signal String Now let’s build it by running this command in the examples/flux-todomvc/ directory: $ elm make TodoStore.elm --output elm-todomvc.js The first time you build, you should see this prompt: Some new packages are needed. Here is the upgrade plan. Install: elm-lang/core 2.1.0 Do you approve of this plan? (y/n) After you answer y, Elm will install your dependencies. When it’s done, you should see this message: Successfully generated elm-todomvc.js. So far, so good! Your directory should now contain an elm-package.json file and the compiled elm-todomvc.js file generated by elm make, as well as an an elm-stuff/ folder which holds downloaded packages and the like. I always add elm-stuff/ to .gitignore. Next we need to add our compiled Elm code to the page. If we’d incorporated gulp-elm into our project’s Gulpfile (see also grunt-elm, elm-webpack-loader, and this Gist for Sprockets integration), then our compiled Elm code would already be included as part of bundle.js. However, we’re avoiding build tool plugins for this example, so we need to tell our index.html file about elm-todomvc.js by adding a new <script> tag above the one that imports "js/bundle.js" like so: ... <script src="elm-todomvc.js"></script><script src="js/bundle.js"></script> /.post-body /.post-popup /.post-foot 21 notes START NOTES manda-cfb liked this evandrix reblogged this from noredinktech evandrix liked this noredinktech posted this END NOTES /.post-notes Source: NoRedInk – Walkthrough: Introducing Elm to a JS Web App

    Read at 03:32 am, Oct 20th

  • Alcántara Defeated Despite Dominican-American Roots

    In a rematch of a Democratic primary race that was decided by just over 500 votes two years ago, former City Council member Robert Jackson defeated incumbent Sen. Marisol Alcántara on Thursday.

    Read at 03:08 am, Oct 20th

Day of Oct 19th, 2019

Day of Oct 18th, 2019

  • This Upper East Side Co-op Is Run Like a Business

    Eight years ago, a revolution took place at the 165-unit co-op at 305 East 72 Street. “We overthrew the board, it was a coup,” says current board president Mark Foley.

    Read at 10:13 pm, Oct 18th

  • Catholic Church, Boy Scouts fight timeline to sue over child abuse

    Pennsylvania state Rep. Tom Murt slid into a pew at his childhood church, seeking a break from politics and the stress of work.  Instead, Murt got an earful.

    Read at 10:08 pm, Oct 18th

  • Designing and Enforcing Codes of Conduct

    It's 2019 and the issue by now seems to be mostly settled: Codes of Conduct, as it turns out, are an important tool for any community to promote inclusion and protect their members from harassment that would otherwise distract them or push them away.

    Read at 09:47 pm, Oct 18th

  • One Night at Mount Sinai

    This article was featured in One Great Story, New York’s reading recommendation newsletter. Sign up here to get it nightly. Sometime after 2 a.m. on January 12, 2016, Aja Newman roused herself from her hospital gurney and made her way down the long hallway to the bathroom.

    Read at 09:44 pm, Oct 18th

  • Much Ado About Bike Sheds, Part II

    This is the second of a series of blog posts about bike sheds. In part one, we explored the history of the bike shed story, you can read part one here.

    Read at 01:57 pm, Oct 18th

  • Much Ado About Bike Sheds, Part I

    If you're working in software, there's good odds that you've run into the phrase "bike shedding" at least once. Maybe you were in a meeting or a group chat and sensed the discussion had wandered a bit.

    Read at 01:48 pm, Oct 18th

  • Why Our Candidates Need to Endorse Bernie

    As the 2020 presidential campaign unfolds, political lines are sharpening among the three leading candidates for the Democratic Party nominee.

    Read at 01:43 pm, Oct 18th

  • Against litmus tests

    A recent article in the Socialist Call argues that DSA should not endorse any candidates that do not endorse Bernie Sanders, and offers support for a resolution to that effect that will come before the NYC-DSA Citywide Leadership Committee this weekend.

    Read at 01:37 pm, Oct 18th

  • First Look at PHP 7.4 for WordPress Developers

    PHP 7.4 is slated for release on November 28, 2019. WordPress 5.3 will also include several commits that address deprecated features. It’s unlikely that most WordPress plugin and theme developers will be able to use the new PHP 7.

    Read at 12:27 pm, Oct 18th

  • [From Android dev] Flutter looks good, but is painful. Here are my frustrations with it.

    Why have I been blocked? This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data. Source: [From Android dev] Flutter looks good, but is painful. Here are my frustrations with it.

    Read at 12:15 pm, Oct 18th

  • How to Make Good Code Reviews Better

    I have been doing day-to-day code reviews for over a decade now. The benefits of code reviews are plenty: someone spot checks your work for errors, they get to learn from your solution, and the collaboration helps to improve the organization’s overall approach to tooling and automation.

    Read at 03:18 am, Oct 18th

Day of Oct 17th, 2019

  • A British family on vacation say they accidentally drove into the U.S. They’ve spent days detained with their 3-month-old baby.

    This article has been updated. The Connors family says it didn’t plan to be on the unmarked road.

    Read at 09:58 pm, Oct 17th

  • What Elijah Cummings Once Told Trump in Private

    After President Donald Trump took office, I spent some time talking to people who had met with him privately in the White House, trying to get a sense of what he’s like when the cameras are off. At the time, I was a reporter at The Wall Street Journal.

    Read at 09:58 pm, Oct 17th

  • Trump has awarded next year’s G-7 summit of world leaders to his Miami-area resort, the White House said

    President Trump has awarded the 2020 Group of Seven summit of world leaders to his private company, scheduling the summit for June at his Trump National Doral Miami golf resort in Florida, the White House announced Thursday.

    Read at 08:34 pm, Oct 17th

  • How I Use tmux For Local Development

    On a recent One on One chat with Brad we got to talking about tmux. Brad was interested to hear that I use it every day on my local development machines, which isn’t what first comes to mind as a use case for tmux.

    Read at 01:28 pm, Oct 17th

  • Democratic presidential hopeful Bernie Sanders to be endorsed by Alexandria Ocasio-Cortez

    Rep. Alexandria Ocasio-Cortez of New York, one of the most influential voices among young liberals and a rising Democratic star, plans to endorse Sen. Bernie Sanders (I-Vt.) for president and appear with him at a rally on Saturday, according to two people with knowledge of her plans.

    Read at 01:24 pm, Oct 17th

  • The Official Zelda Timeline, Now With Added Detail

    While we were able last week to bring you the official Zelda timeline, courtesy of the series' new art book, it was only the barest of structures, with just game names in chronological order. Now that the book has been out for a few days, more comprehensive translations are available.

    Read at 01:23 pm, Oct 17th

  • This Might Actually Be The Official Zelda Timeline

    Since the series began, Nintendo has been coy on whether there's an official timeline for The Legend of Zelda. They've said there is one, locked away in Nintendo HQ, but the fact it's never been shared - and that each game seems so wildly different from the rest - has long clouded the topic.

    Read at 01:12 pm, Oct 17th

  • I’m a Black Feminist. I Think Call-Out Culture Is Toxic.

    There are better ways of doing social justice work. Ms. Ross, an expert on women’s issues, racism and human rights, is a founder of the reproductive justice theory.

    Read at 01:10 pm, Oct 17th

  • How much faster is Redis at storing a blob of JSON compared to PostgreSQL? - Peterbe.com

    tl;dr; Redis is 16 times faster at reading these JSON blobs. In Song Search when you've found a song, it loads some affiliate links to Amazon.com. (In case you're curious it's earning me lower double-digit dollars per month). To avoid overloading the Amazon Affiliate Product API, after I've queried their API, I store that result in my own database along with some metadata. Then, the next time someone views that song page, it can read from my local database. With me so far? The other caveat is that you can't store these lookups locally too long since prices change and/or results change. So if my own stored result is older than a couple of hundred days, I delete it and fetch from the network again. My current implementation uses PostgreSQL (via the Django ORM) to store this stuff. The model looks like this: class AmazonAffiliateLookup(models.Model, TotalCountMixin): song = models.ForeignKey(Song, on_delete=models.CASCADE) matches = JSONField(null=True) search_index = models.CharField(max_length=100, null=True) lookup_seconds = models.FloatField(null=True) created = models.DateTimeField(auto_now_add=True, db_index=True) modified = models.DateTimeField(auto_now=True) At the moment this database table is 3GB on disk. Then, I thought, why not use Redis for this. Then I can use Redis's "natural" expiration by simply setting as expiry time when I store it and then I don't have to worry about cleaning up old stuff at all. The way I'm using Redis in this project is as a/the cache backend and I have it configured like this: CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": REDIS_URL, "TIMEOUT": config("CACHE_TIMEOUT", 500), "KEY_PREFIX": config("CACHE_KEY_PREFIX", ""), "OPTIONS": { "COMPRESSOR": "django_redis.compressors.zlib.ZlibCompressor", "SERIALIZER": "django_redis.serializers.msgpack.MSGPackSerializer", }, } } The speed difference Perhaps unrealistic but I'm doing all this testing here on my MacBook Pro. The connection to Postgres (version 11.4) and Redis (3.2.1) are both on localhost. Reads The reads are the most important because hopefully, they happen 10x more than writes as several people can benefit from previous saves. I changed my code so that it would do a read from both databases and if it was found in both, write down their time in a log file which I'll later summarize. Results are as follows: PG: median: 8.66ms mean : 11.18ms stdev : 19.48ms Redis: median: 0.53ms mean : 0.84ms stdev : 2.26ms (310 measurements) It means, when focussing on the median, Redis is 16 times faster than PostgreSQL at reading these JSON blobs. Writes The writes are less important but due to the synchronous nature of my Django, the unlucky user who triggers a look up that I didn't have, will have to wait for the write before the XHR request can be completed. However, when this happens, the remote network call to the Amazon Product API is bound to be much slower. Results are as follows: PG: median: 8.59ms mean : 8.58ms stdev : 6.78ms Redis: median: 0.44ms mean : 0.49ms stdev : 0.27ms (137 measurements) It means, when focussing on the median, Redis is 20 times faster than PostgreSQL at writing these JSON blobs. Conclusion and discussion First of all, I'm still a PostgreSQL fan-boy and have no intention of ceasing that. These times are made up of much more than just the individual databases. For example, the PostgreSQL speeds depend on the Django ORM code that makes the SQL and sends the query and then turns it into the model instance. I don't know what the proportions are between that and the actual bytes-from-PG's-disk times. But I'm not sure I care either. The tooling around the database is inevitable mostly and it's what matters to users. Both Redis and PostgreSQL are persistent and survive server restarts and crashes etc. And you get so many more "batch related" features with PostgreSQL if you need them, such as being able to get a list of the last 10 rows added for some post-processing batch job. I'm currently using Django's cache framework, with Redis as its backend, and it's a cache framework. It's not meant to be a persistent database. I like the idea that if I really have to I can just flush the cache and although detrimental to performance (temporarily) it shouldn't be a disaster. So I think what I'll do is store these JSON blobs in both databases. Yes, it means roughly 6GB of SSD storage but it also potentially means loading a LOT more into RAM on my limited server. That extra RAM usage pretty much sums of this whole blog post; of course it's faster if you can rely on RAM instead of disk. Now I just need to figure out how RAM I can afford myself for this piece and whether it's worth it. UPDATE September 29, 2019 I experimented with an optimization of NOT turning the Django ORM query into a model instance for each record. Instead, I did this: +from dataclasses import dataclass +@dataclass +class _Lookup: + modified: datetime.datetime + matches: list ... +base_qs = base_qs.values_list("modified", "matches") -lookup = base_qs.get(song__id=song_id) +lookup_tuple = base_qs.get(song__id=song_id) +lookup = _Lookup(*lookup_tuple) print(lookup.modified) Basically, let the SQL driver's "raw Python" content come through the Django ORM. The old difference between PostgreSQL and Redis was 16x. The new difference was 14x instead. Follow @peterbe on Twitter Previous: uwsgi weirdness with --http 19 September 2019 Next: Update to speed comparison for Redis vs PostgreSQL storing blobs of JSON 30 September 2019 Related by Keyword: Build an XML sitemap of XML sitemaps 01 June 2019 Django ORM optimization story on selecting the least possible 22 February 2019 Fastest *local* cache backend possible for Django 04 August 2017 Fastest Redis configuration for Django 11 May 2017 Fastest cache backend possible for Django 07 April 2017 Source: How much faster is Redis at storing a blob of JSON compared to PostgreSQL? – Peterbe.com

    Read at 12:10 pm, Oct 17th

  • How to work with Postgres in Go - AvitoTech - Medium

    Why have I been blocked? This website is using a security service to protect itself from online attacks. The action you just performed triggered the security solution. There are several actions that could trigger this block including submitting a certain word or phrase, a SQL command or malformed data. Source: How to work with Postgres in Go – AvitoTech – Medium

    Read at 12:07 pm, Oct 17th

  • After 30 days, GM-UAW talks suddenly face a deadline

    The clock is ticking for General Motors executives to reach a proposed tentative agreement with the UAW, people close to the talks said Tuesday.

    Read at 12:41 am, Oct 17th

  • Kickstarter Calls Itself Progressive. But About That Union.

    A contentious organizing drive at the Brooklyn tech company has drawn in high-profile figures like the actor David Cross, the author Neil Gaiman and the cartoonist Matt Bors.

    Read at 12:39 am, Oct 17th

  • Gatsby Days London Features Multiple WordPress Presentations

    Gatsby Days London kicked off yesterday with a lot of momentum after Gatsby Inc. announced a $15M Series A funding round last week. The one-day conference is the third in a series of Gatsby Days that have been held in other cities.

    Read at 12:31 am, Oct 17th